本文基于android9.0来写的。

一、产品需求:如下图,类似mac的桌面系统

(1)区域1是系统标题栏。

(2)区域2是长显示的窗口。

(3)区域3 普通app显示的窗口。

(4) 区域4 也是一个上显示的窗口,主要用于应用的点击启动。

二、下图是根据Android系统特性画的草图:

(1)区域1是系统statusBar。

(2)区域2是长显示的窗口,用WindowMananger添加的窗口。

(3)区域3 普通app显示的窗口,定制系统默认窗口大小,定位至区域3。

(4) 区域4 也是一个长显示的窗口,同样是用WindowMananger添加的窗口。

三、主要实现思路:通过freefrom 和添加悬浮window来实现。因为app只有在freeform模式才能任意定制多窗口大小和位置。

1、区域1无需改动,还是采用系统默认stausBar。

2、修改famework AMS ,以freeform模式启动Launcher。修改HOME的的区域为上图区域3。

3、Lancher启动时,同时启动两个两个service, 然后通过WindowManger来添加区域2和区域4。

4、区域4 的Icon点击以freeform模式启动其他APP。在framework拦截启动APP的入口,以freeform模式启动,同时设置窗口大小和位置(区域3)。

四、最终实现的demo如下:

五、具体实现方法如下:

1、开启freeform

google 默认freeform是关闭的,如果要测试需要手动开启。

配置config_freeformWindowManagement

WORKING_DIRECTORY/frameworks/base/core/res/res/values/config.xml

如下,将config_freeformWindowManagement配置为true

WORKING_DIRECTORY/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

private void retrieveSettings() {

final ContentResolver resolver = mContext.getContentResolver();

//获取是否支持freeform

final boolean freeformWindowManagement =

mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)

|| Settings.Global.getInt(

resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;

由上面代码可以看出有两种方法可以开启freeform

(1)  Settings 设置开启的属性

WORKING_DIRECTORY/frameworks/base/core/java/android/provider/Settings.java

/**

* Whether any activity can be resized. When this is true, any

* activity, regardless of manifest values, can be resized for multi-window.

* (0 = false, 1 = true)

* @hide

*/

public static final String DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES

= "force_resizable_activities";

/**

* Whether to enable experimental freeform support for windows.

* @hide

*/

public static final String DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT

= "enable_freeform_support";

这个用命令就可以开启:

adb shell settings put global enable_freeform_support  1

adb shell settings put global force_resizable_activities  1

如果要在framework中默认开启,可以这样:

private void retrieveSettings() {

final ContentResolver resolver = mContext.getContentResolver();

//简单粗暴开启,哈哈

Settings.Global.putInt(resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 1);

Settings.Global.putInt(resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 1);

final boolean freeformWindowManagement =

mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)

|| Settings.Global.getInt(

resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;

(2)给系统添加feature:android.software.freeform_window_management

其实android系统默认已经写好,这里可以找到:

WORKING_DIRECTORY/frameworks/native/data/etc

可以将android.software.freeform_window_management.xml 拷贝到WORKING_DIRECTORY/frameworks/base/data/etc

里面有个Android.mk在这里面添加如下:

########################

include $(CLEAR_VARS)

LOCAL_MODULE := android.software.freeform_window_management.xml

LOCAL_MODULE_CLASS := ETC

LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions

LOCAL_SRC_FILES := $(LOCAL_MODULE)

include $(BUILD_PREBUILT)

它最终会被拷贝到手机的system/etc/permissions/目录下,开机时PMS会通过的读取此目录下xml配置开启freeform这个feature.

但是经过测试这个还是不够,还需要开启

Settings.Global.putInt(resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 1);

2、freeform模式启动app

Android为了支持多窗口,在运行时创建了多个Stack,Stack就是类似这里虚拟桌面的作用。

每个Stack会有一个唯一的Id,在WindowConfiguration.java中定义了这些Stack的Id:如下:

public class WindowConfiguration

implements android.os.Parcelable, java.lang.Comparable{

public WindowConfiguration() { throw new RuntimeException("Stub!"); }

public void writeToParcel(android.os.Parcel dest, int flags) { throw new RuntimeException("Stub!"); }

public int describeContents() { throw new RuntimeException("Stub!"); }

public void setBounds(android.graphics.Rect rect) { throw new RuntimeException("Stub!"); }

public void setAppBounds(android.graphics.Rect rect) { throw new RuntimeException("Stub!"); }

public android.graphics.Rect getAppBounds() { throw new RuntimeException("Stub!"); }

public android.graphics.Rect getBounds() { throw new RuntimeException("Stub!"); }

public void setWindowingMode(int windowingMode) { throw new RuntimeException("Stub!"); }

public int getWindowingMode() { throw new RuntimeException("Stub!"); }

public void setActivityType(int activityType) { throw new RuntimeException("Stub!"); }

public int getActivityType() { throw new RuntimeException("Stub!"); }

public void setTo(android.app.WindowConfiguration other) { throw new RuntimeException("Stub!"); }

public int compareTo(android.app.WindowConfiguration that) { throw new RuntimeException("Stub!"); }

public static final int ACTIVITY_TYPE_ASSISTANT = 4;

public static final int ACTIVITY_TYPE_HOME = 2;

public static final int ACTIVITY_TYPE_RECENTS = 3;

public static final int ACTIVITY_TYPE_STANDARD = 1;

public static final int ACTIVITY_TYPE_UNDEFINED = 0;

public static final int WINDOWING_MODE_FREEFORM = 5;

public static final int WINDOWING_MODE_FULLSCREEN = 1;

public static final int WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY = 4;

public static final int WINDOWING_MODE_PINNED = 2;

public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;

public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;

public static final int WINDOWING_MODE_UNDEFINED = 0;

}

例如以freeform启动activity

Intent intent = new Intent(this, AdjacentActivity.class);

intent.setFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT | Intent.FLAG_ACTIVITY_NEW_TASK);

ActivityOptions activityOptions = ActivityOptions.makeBasic();

activityOptions.setLaunchWindowingMode(5);

int left = 100;

int top = 0;

int right = 720;

int bottom = 2280;

activityOptions.setLaunchBounds(new Rect(left,top,right,bottom));

Bundle bundle = activityOptions.toBundle();

startActivity(intent,bundle)

setLaunchWindowingMode 是个testApi,我们开发的时候获取不到,可以通过反射来调用

public static ActivityOptions getActivityOptions(Context context) {

ActivityOptions options = ActivityOptions.makeBasic();

int freeform_stackId = 5;

try {

Method method = ActivityOptions.class.getMethod("setLaunchWindowingMode", int.class);

method.invoke(options, freeform_stackId);

} catch (Exception e) { /* Gracefully fail */ }

return options;

}

具体详细用法可以参考

https://github.com/farmerbb/Taskbar

3、如何以freeform 启动launcher

/WORKING_DIRECTORY/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

boolean startHomeActivityLocked(int userId, String reason) {

if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL

&& mTopAction == null) {

// We are running in factory test mode, but unable to find

// the factory test app, so just sit around displaying the

// error message and don't try to start anything.

return false;

}

Intent intent = getHomeIntent();

ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);

if (aInfo != null) {

intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));

// Don't do this if the home app is currently being

// instrumented.

aInfo = new ActivityInfo(aInfo);

aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);

ProcessRecord app = getProcessRecordLocked(aInfo.processName,

aInfo.applicationInfo.uid, true);

if (app == null || app.instr == null) {

intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK);

final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid);

// For ANR debugging to verify if the user activity is the one that actually

// launched.

final String myReason = reason + ":" + userId + ":" + resolvedUserId;

//这里调用启动homeActivity

mActivityStartController.startHomeActivity(intent, aInfo, myReason);

}

} else {

Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());

}

return true;

}

/WORKING_DIRECTORY/frameworks/base/services/core/java/com/android/server/am/ActivityStartController.java

void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason) {

mSupervisor.moveHomeStackTaskToTop(reason);

// 这里我设置了freefrom启动参数。

ActivityOptions activityOptions = ActivityOptions.makeBasic();

activityOptions.setLaunchWindowingMode(5);

Point size = new Point();

mService.mWindowManager.getInitialDisplaySize(0,size);

int left = 100;

int top = 0;

int right = size.x;

int bottom = size.y*6/7;

activityOptions.setLaunchBounds(new Rect(left,top,right,bottom));

Bundle bundle = activityOptions.toBundle();

mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)

.setOutActivity(tmpOutRecord)

.setCallingUid(0)

.setActivityInfo(aInfo)

.setActivityOptions(bundle)

.execute();

mLastHomeActivityStartRecord = tmpOutRecord[0];

if (mSupervisor.inResumeTopActivity) {

// If we are in resume section already, home activity will be initialized, but not

// resumed (to avoid recursive resume) and will stay that way until something pokes it

// again. We need to schedule another resume.

mSupervisor.scheduleResumeTopActivities();

}

}

WORKING_DIRECTORY/frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

作如下修改:

// Note: This method should only be called from {@link startActivity}.

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,

ActivityRecord[] outActivity) {

setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,

voiceInteractor);

computeLaunchingTaskFlags();

computeSourceStack();

mIntent.setFlags(mLaunchFlags);

ActivityRecord reusedActivity = getReusableIntentActivity();

int preferredWindowingMode = WINDOWING_MODE_UNDEFINED;

int preferredLaunchDisplayId = DEFAULT_DISPLAY;

//在这里对启动的activity作拦截,用freeform启动,设置setLaunchWindowingMode 为5

Point size = new Point();

Display display = mSupervisor.mDisplayManager.getDisplay(0);

DisplayMetrics displayMetrics = new DisplayMetrics();

display.getMetrics(displayMetrics);

size.x = displayMetrics.widthPixels;

size.y = displayMetrics.heightPixels;

Slog.e("ZyTest", "myTestZize displayMetrics w = " + displayMetrics.widthPixels + " h = " + displayMetrics.heightPixels);

if (mOptions != null) {

preferredWindowingMode = mOptions.getLaunchWindowingMode();

preferredLaunchDisplayId = mOptions.getLaunchDisplayId();

Slog.e("ZyTest", "preferredWindowingMode = " + preferredWindowingMode );

int left = size.x /4;

int top = 0;

int right = size.x;

int bottom = size.y*6/7;;

mOptions.setLaunchBounds(new Rect(left,top,right,bottom));

mOptions.setLaunchWindowingMode(5);

}else {

mOptions = ActivityOptions.makeBasic();

int left = size.x /4;

int top = 0;

int right = size.x;

int bottom = size.y*6/7;;

mOptions.setLaunchBounds(new Rect(left,top,right,bottom));

mOptions.setLaunchWindowingMode(5);

Slog.e("ZyTest", "22222 preferredWindowingMode = " + preferredWindowingMode );

}

preferredWindowingMode = 5;

// windowing mode and preferred launch display values from {@link LaunchParams} take

// priority over those specified in {@link ActivityOptions}.

if (!mLaunchParams.isEmpty()) {

if (mLaunchParams.hasPreferredDisplay()) {

preferredLaunchDisplayId = mLaunchParams.mPreferredDisplayId;

}

if (mLaunchParams.hasWindowingMode()) {

preferredWindowingMode = mLaunchParams.mWindowingMode;

}

}

设置启动的目标栈的setWindowingMode为freeform,位置和大小自定义

private int setTaskFromReuseOrCreateNewTask(

TaskRecord taskToAffiliate, ActivityStack topStack) {

mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);

boolean ishome = mStartActivity.isActivityTypeHome();

Slog.e("ZyTest", "is home llllllllll = " + ishome);

// if(ishome){

Point size = new Point();

// mService.mWindowManager.getInitialDisplaySize(0,size);

//设置目标栈的setWindowingMode、位置 、大小

Display display = mSupervisor.mDisplayManager.getDisplay(0);

DisplayMetrics displayMetrics = new DisplayMetrics();

display.getMetrics(displayMetrics);

size.x = displayMetrics.widthPixels;

size.y = displayMetrics.heightPixels;

int left = size.x /4;

int top = 0;

int right = size.x;

int bottom = size.y*6/7;

mTargetStack.setBounds(left,top,right,bottom);

mTargetStack.setWindowingMode(5);

// }

// Do no move the target stack to front yet, as we might bail if

// isLockTaskModeViolation fails below.

if (mReuseTask == null) {

final TaskRecord task = mTargetStack.createTaskRecord(

mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),

mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,

mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,

mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,

mOptions);

addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");

updateBounds(mStartActivity.getTask(), mLaunchParams.mBounds);

if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity

+ " in new task " + mStartActivity.getTask());

} else {

addOrReparentStartingActivity(mReuseTask, "setTaskFromReuseOrCreateNewTask");

}

if (taskToAffiliate != null) {

mStartActivity.setTaskToAffiliateWith(taskToAffiliate);

}

if (mService.getLockTaskController().isLockTaskModeViolation(mStartActivity.getTask())) {

Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);

return START_RETURN_LOCK_TASK_MODE_VIOLATION;

}

if (mDoResume) {

mTargetStack.moveToFront("reuseOrNewTask");

}

return START_SUCCESS;

}

android free form,android freeform模式定制桌面系统相关推荐

  1. android自由窗口freeform模式的实现

    freeform模式默认关闭,一些手机厂商开启了此功能,模拟器没有开启.依次执行下面命令: adb shell settings put global enable_freeform_support ...

  2. android的四种启动模式,(转)彻底弄懂Activity四大启动模式

    原地址:https://blog..net/mynameishuangshuai/article/details/51491074 最近有几位朋友给我留言,让我谈一下对Activity启动模式的理解. ...

  3. 鸿蒙桌面系统什么时候上线,鸿蒙OS全新PC桌面模式即将上线?回顾一下手机桌面系统的发展历程...

    说到在手机操作系统中内置大屏幕桌面模式,那要追溯到2011年,那一年摩托罗拉发布了一款型号为ME860(Atrix 4G)的安卓手机,采用4.0英寸的电容触摸屏,后置指纹识别,搭载英伟达Tegra 2 ...

  4. 华为鸿蒙桌面,鸿蒙OS全新PC桌面模式即将上线?回顾一下手机桌面系统的发展历程...

    说到在手机操作系统中内置大屏幕桌面模式,那要追溯到2011年,那一年摩托罗拉发布了一款型号为ME860(Atrix 4G)的安卓手机,采用4.0英寸的电容触摸屏,后置指纹识别,搭载英伟达Tegra 2 ...

  5. 鸿蒙os骁龙845,鸿蒙OS全新PC桌面模式即将上线?回顾一下手机桌面系统的发展历程...

    说到在手机操作系统中内置大屏幕桌面模式,那要追溯到2011年,那一年摩托罗拉发布了一款型号为ME860(Atrix 4G)的安卓手机,采用4.0英寸的电容触摸屏,后置指纹识别,搭载英伟达Tegra 2 ...

  6. 鸿蒙os更换壁纸,鸿蒙OS全新PC桌面模式即将上线?回顾一下手机桌面系统的发展历程...

    说到在手机操作系统中内置大屏幕桌面模式,那要追溯到2011年,那一年摩托罗拉发布了一款型号为ME860(Atrix 4G)的安卓手机,采用4.0英寸的电容触摸屏,后置指纹识别,搭载英伟达Tegra 2 ...

  7. Android Freeform模式

    Android N引入了Multi-Window, Freeform自由窗口模式是其中的一种.自由窗口模式下可以实现窗口的可以自由缩放,自由移动. 1. Freeform功能开启 Android原生版 ...

  8. android实现电量控制,Android 删除/定制桌面电量控制插件

    这个插件是属于设置的一部分,可以在 packages\apps\Settings\res\drawable-hdpi 这个文件夹里面查看,呵呵,有没有看到上面目录的斜杠方向,懂吧. 这里找到一个文件名 ...

  9. android 平板桌面,给Android平板带点桌面系统体验:技德 Remix 平板 下月上市

    给Android平板带点桌面系统体验:技德 Remix 平板 下月上市 2015-01-27 21:50:31 29点赞 14收藏 18评论 此前在众测频道接受值友们检验的技德Remix Androi ...

最新文章

  1. 在Matlab中调用Mathematica的函数
  2. Know more about AWR Parse Statistics
  3. ASP.NET Web API中实现版本
  4. [转载]Bluetooth协议栈学习之SDP
  5. 用python一行代码实现1—100之和,你会吗
  6. eclipse中如何搜索带\的字串
  7. mysql死锁案例及解决方案_MySQL死锁案例分析与解决方案
  8. 【JavaSE】黑马程序员 刘意 基础部分笔记
  9. 霍常亮淘宝客教你开发app第10节:搭建uni-app开发环境Hbuilder
  10. 主题:基于非合作博弈模型多微网交易策略研究 参考文档:《基于博弈论的多微电网系统交易模式研究》完全复现
  11. PCB设计及硬件编程学习
  12. BI大数据分析是什么,大数据bi工具有哪些
  13. 下载喜马拉雅FM的音频
  14. (三)5.自动控制原理 Time domain analysis and correct 稳态误差
  15. 提高代码质量——使用Jest和Sinon给已有的代码添加单元测试
  16. 初学SpringMVC注册前端控制器DispatcherServlet:org.springframework.web.servlet.DispatcherServlet报红
  17. 防止域名被劫持的七种方法域名被劫持怎么办
  18. 图像处理的数学模型与高性能算法——介绍
  19. java 写一个quot;HelloJavaWorld你好世界quot;输出到操作系统文件Hello.txt文件中
  20. 【SAP】PP模块术语

热门文章

  1. 矩阵半高宽matlab,半导体激光器半高宽(FWHM)计算(包含matlab仿真程序)
  2. 机器学习K均值聚类 python
  3. 微信小程序调用阿里OCR识别
  4. 如何使用MATLAB将两张或者多张figure图形合并到一个figure图形里进行对比
  5. 谷歌眼镜活下去的唯一希望:开放软件系统
  6. 马赛克拼接图片生成器done~
  7. 计算2的幂(C语言)
  8. 安装包资源下载(暂整理)
  9. NYOJ 会场安排问题
  10. Java基础高频面试题