Android开机设置默认Launcher

讲到设置默认Launcher,必须先说说Provision.apk这个应用,这个是Android原生自带的应用,本文讲解设置默认Launcher就是通过Provision.apk这个应用来实现的

首先我们知道Android启动HomeLauncher的最后一步是在ActivityManagerService.java中的startHomeActivityLocked方法中发送一个带有android.intent.category.HOME
的intent来启动响应该属性的应用,一般Launcher的Manifest配置文件中都会有此属性,所以系统中的Launcher就是这样被启动了。关于Android启动Launcher的流程,找时间可以再写一篇文章分析,暂时介绍到这里

接下来就要说Provision这个应用了,该应用的源码在packages/apps/下的Provision这个文件夹中,看一下该应用的目录结构:

该应用只有一个java文件DefaultActivity.java,我们看一下该应用的配置文件AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- For miscellaneous settings -->
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

<application>
<activity android:name="DefaultActivity"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>

我们注意到其实Provision也会响应android.intent.category.HOME属性,并且从这句android:priority=1可以看出起优先级是比一般Launcher都要高的,所以在AMS的startHomeActivityLocked启动Launcher的时候会先启动Provision应用,然后才是启动HomeLauncher,那接着我们看看在Provision中都做了些什么,我们知道默认是启动DefaultActivity.java这个文件,看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Application that sets the provisioned bit, like SetupWizard does.
*/
public class DefaultActivity extends Activity {

@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);

// Add a persistent setting to allow other apps to know the device has been provisioned.
Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1);

// remove this activity from the package manager.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, DefaultActivity.class);
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
// terminate the activity.
finish();
}
}

代码很简洁,首先是在Settings的Global表中将DEVICE_PROVISIONED属性设置为1(这一点还是相当重要的,因为很多地方都会检查该标志是否被置成1来决定是不是要走正常的流程),在Secure表中将USER_SETUP_COMPLETE属性设置为1。其实获取一个PackageManager对象,通过该对象的setComponentEnabledSetting方法来吧自己给disable掉。看一下该方法的解释和说明:

1
2
3
4
5
6
7
void setComponentEnabledSetting (ComponentName componentName, int newState, int flags)
componentName:组件名称
newState:组件新的状态,可以设置三个值,分别是如下:
不可用状态:COMPONENT_ENABLED_STATE_DISABLED
可用状态:COMPONENT_ENABLED_STATE_ENABLED
默认状态:COMPONENT_ENABLED_STATE_DEFAULT
flags:行为标签,值可以是DONT_KILL_APP或者0。 0说明杀死包含该组件的app

流程很清楚,Provision将DEVICE_PROVISIONED标志设置成1之后,然后就将自己从PM中removed掉了,以后就不会再启动,除非系统被重置,或者data目录被删除

接下来就是讲如何设置默认Launcher了,思路就是在Provision中设置好默认Launcher,Provision启动之后就直接启动默认Launcher了,就不会存在如果系统中有多个Launcher的时候,弹出让用户选择的情况了。

首先说一下我们公司的需要,我们智能电视默认带有三个Launcher,然后默认Launcher是放到system/etc下面的一个DefaultLauncher的配置文件中,第一次启动系统的时候就是根据改配置文件来决定默认Launcher的,首先看一下我们是怎么修改DefaultActivity.java文件的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
public class DefaultActivity extends Activity {
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Add a persistent setting to allow other apps to know the device has been provisioned.
Settings.Global.putInt(getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 1);
Settings.Secure.putInt(getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 1);

// remove this activity from the package manager.
PackageManager pm = getPackageManager();
ComponentName name = new ComponentName(this, DefaultActivity.class);
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
setDefaultLauncher();
// terminate the activity.
finish();
}

private String getDefaultLauncherPkgName() {
final String path = "/system/etc/defaultlauncher.dat";
System.out.println("in getDefaultLauncherPkgName");
String PackageName = null;
File f = new File(path);
if (!f.exists()) {
System.out.println("/system/etc/defaultlauncher.dat is not exit");
return null;
}
if (f == null) {
System.out.println("File f is null");
return null;
}
try {
FileReader r = new FileReader(f);
BufferedReader br = new BufferedReader(r);
String temp = null;
try {
while ((temp = br.readLine()) != null) {
if (temp.indexOf("defaultlauncher") != -1) {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(temp);
String dest = m.replaceAll("");
PackageName = (dest).substring((dest).indexOf("=") + 1);
break;
}
}
br.close();
r.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
System.out.println("getDefaultLauncherPkgName PackageName = " + PackageName);
return PackageName;
}

private boolean setDefaultLauncher() {
// get default component
System.out.println("Come in setDefaultLauncher");
PackageManager pm = getPackageManager();
ArrayList<IntentFilter> LauncherIntentList = new ArrayList<IntentFilter>();
ArrayList<ComponentName> LauncherList = new ArrayList<ComponentName>();
//clear the current preferred launcher
ArrayList<IntentFilter> intentList = new ArrayList<IntentFilter>();
ArrayList<ComponentName> cnList = new ArrayList<ComponentName>();
pm.getPreferredActivities(intentList, cnList, null);
IntentFilter dhIF;
for (int i = 0; i < cnList.size(); i++) {
dhIF = intentList.get(i);
if (dhIF.hasAction(Intent.ACTION_MAIN) && dhIF.hasCategory(Intent.CATEGORY_HOME)) {
LauncherIntentList.add(intentList.get(i));
LauncherList.add(cnList.get(i));
}
}
if (LauncherList.size() == 1) {
System.out.println("Already have set default launcher");
return true;
}
if (LauncherList.size() > 1) {
for (int i = 0; i < LauncherList.size(); i++)
pm.clearPackagePreferredActivities(LauncherList.get(i).getPackageName());
}
String defaultLauncherPkg = getDefaultLauncherPkgName();
if (defaultLauncherPkg == null) {
System.out.println("In ActivityManagerService setDefaultLauncher defaultLauncherPkg = null");
return false;
}
// get all Launcher activities
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> list = new ArrayList<ResolveInfo>();
list = pm.queryIntentActivities(intent, 0);
// get all components and the best match
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MAIN);
filter.addCategory(Intent.CATEGORY_HOME);
filter.addCategory(Intent.CATEGORY_DEFAULT);
final int N = list.size();
ComponentName[] set = new ComponentName[N];
int bestMatch = IntentFilter.MATCH_CATEGORY_EMPTY;
int defaultLauncherIndex = -1;
for (int i = 0; i < N; i++) {
ResolveInfo r = list.get(i);
set[i] = new ComponentName(r.activityInfo.packageName, r.activityInfo.name);
if (defaultLauncherPkg.equals(r.activityInfo.packageName))
defaultLauncherIndex = i;
System.out.println("SetDefaultLauncher: " + set[i].getPackageName() + " " + set[i].getClassName());
//if (r.match > bestMatch) bestMatch = r.match;
}
// add the default launcher as the preferred launcher
if (defaultLauncherIndex == -1) {
return false;
}
ComponentName launcher = new ComponentName(set[defaultLauncherIndex].getPackageName(),
set[defaultLauncherIndex].getClassName());
pm.addPreferredActivity(filter, bestMatch, set, launcher);
return true;
}
}

这里需要重点说明的就是这两个方法:

1
2
3
4
//清除之前默认选择的Launcher
pm.clearPackagePreferredActivities(LauncherList.get(i).getPackageName());
//添加默认Launcher
pm.addPreferredActivity(filter, bestMatch, set, launcher);

PS:这两句能够执行成功的前提是必须在AndroidManifest.xml文件中加入权限:

1
2
3
4
5
6
<!-- For miscellaneous settings -->
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<!-- Konka Patch Start -->
<uses-permission android:name="android.permission.SET_PREFERRED_APPLICATIONS"/>
<!-- Konka Patch End -->

这样系统开机就会进入默认设置的Launcher了,用户可以在我这基础上修改默认Launcher的设置方式,或者在Provision程序中设置一些需要在启动Launcher之前进行的设置等等

网上还有其他一些设置默认Launcher的方式,亲测也是可以的,列举如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
readPermissions();
String firstBoot = SystemProperties.get("persist.sys.preactivity");
if ("0".equals(firstBoot)) {
addPreferredActivityForULauncher();
}
SystemProperties.set("persist.sys.preactivity", "1");
mRestoredSettings = mSettings.readLPw();
private void addPreferredActivityForULauncher() {
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.MAIN");
filter.addCategory("android.intent.category.HOME");
filter.addCategory("android.intent.category.DEFAULT");
ComponentName preActivity = new ComponentName("com.android.ulauncher"
,"com.android.ulauncher.Launcher");
ComponentName[] set = new ComponentName[] {
new ComponentName("com.android.launcher",
"com.android.launcher2.Launcher"),preActivity
};
mSettings.mPreferredActivities.addFilter(
new PreferredActivity(filter,
IntentFilter.MATCH_CATEGORY_EMPTY, set, preActivity));
scheduleWriteSettingsLocked();
}

在startHomeActivityLocked方法中加入setDefaultLauncher方法,感兴趣的大家可以继续研究,亲身经历才会有大的收获。