Android Launcher3 隐藏应用图标和widget的显示

在做Launcher开发的时候,有时候需要隐藏应用图标的显示或者隐藏某些应用的widget显示,这个时候怎么办呢?

查看Launcher3的源码,发现其实源码里已经提供了相关的实现,发现有一个AppFilter的抽象类,看名字就知道这是一个app过滤器,看看这个接口的源码:

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
package com.codemx.launcher3;

import android.content.ComponentName;
import android.text.TextUtils;
import android.util.Log;

public abstract class AppFilter {

private static final boolean DBG = false;
private static final String TAG = "AppFilter";

public abstract boolean shouldShowApp(ComponentName app);

public static AppFilter loadByName(String className) {
if (TextUtils.isEmpty(className)) return null;
if (DBG) Log.d(TAG, "Loading AppFilter: " + className);
try {
Class<?> cls = Class.forName(className);
return (AppFilter) cls.newInstance();
} catch (ClassNotFoundException e) {
Log.e(TAG, "Bad AppFilter class", e);
return null;
} catch (InstantiationException e) {
Log.e(TAG, "Bad AppFilter class", e);
return null;
} catch (IllegalAccessException e) {
Log.e(TAG, "Bad AppFilter class", e);
return null;
} catch (ClassCastException e) {
Log.e(TAG, "Bad AppFilter class", e);
return null;
}
}

}

提供了一个shouldShowApp的抽象方法,顾名思义就是判断哪些应用需要显示,通过这个方法来过滤。另外还提供了一个loadByName的静态方法,看实现是根据className通过反射实例化一个AppFilter的实现。

接下看看那个类继承了AppFilter这个抽象类?一番查找发现源码里并没有找到。既然是通过loadByName实例化的那就看看这个方法在哪里用到了,最后发现在LauncherAppState.java的构造方法里调用了:

1
2
3
4
5
6
7
8
9
private LauncherAppState() {
...

mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
mModel = new LauncherModel(this, mIconCache, mAppFilter);

...
}

这里调用了,传的参数是sContext.getString(R.string.app_filter_class)是一个字符串资源,来看看这个字符串的值,在config.xml里:

1
2
3
<!-- Name of a subclass of AppFilter used to
filter the activities shown in the launcher. Can be empty. -->
<string name="app_filter_class" translatable="false"></string>

发现配置的值是空的,也就是默认是没有实现类的。那是不是我们写一个类继承AppFilter这个类然后添加到这个配置就行了呢?
写一个简单的实现类AppFilterImpl:

1
2
3
4
5
6
7
public class AppFilterImpl extends AppFilter {

@Override
public boolean shouldShowApp(ComponentName app) {
return !"com.android.email".equals(app.getPackageName());
}
}

很简单,判断如果报名不是邮箱就显示,也就是隐藏邮箱app图标的显示。然后把这个自定义的类配置到app_filter_class上:

1
2
3
<!-- Name of a subclass of AppFilter used to
filter the activities shown in the launcher. Can be empty. -->
<string name="app_filter_class" translatable="false">com.android.launcher3.AppFilterImpl</string>

运行,然后看桌面和Launcher的应用列表里都没有了,并且小部件里也找不到邮箱的widget了,看来确实起作用了。那接下来我们看看是怎么实现的呢。

查看shouldShowApp方法在两个地方调用了一个是在AllAppsList的add方法里,一个是在WidgetsModel
先看看AllAppsList里的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Add the supplied ApplicationInfo objects to the list, and enqueue it into the
* list to broadcast when notify() is called.
*
* If the app is already in the list, doesn't add it.
*/
public void add(AppInfo info) {
if (mAppFilter != null && !mAppFilter.shouldShowApp(info.componentName)) {
return;
}
if (findActivity(data, info.componentName, info.user)) {
return;
}
data.add(info);
added.add(info);
}

很明显,在添加appinfo的时候调用了mAppFilter.shouldShowApp,当返回false的时候就直接return了,没有添加到应用列表里去,所以就不会显示。

再看看widget的过滤WidgetsModel.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
public void setWidgetsAndShortcuts(ArrayList<Object> rawWidgetsShortcuts) {
Utilities.assertWorkerThread();
mRawList = rawWidgetsShortcuts;
if (DEBUG) {
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
}

// Temporary list for {@link PackageItemInfos} to avoid having to go through
// {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
HashMap<String, PackageItemInfo> tmpPackageItemInfos = new HashMap<>();

// clear the lists.
mWidgetsList.clear();
mPackageItemInfos.clear();
mWidgetAndShortcutNameComparator.reset();

InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();

// add and update.
for (Object o: rawWidgetsShortcuts) {
String packageName = "";
UserHandleCompat userHandle = null;
ComponentName componentName = null;
if (o instanceof LauncherAppWidgetProviderInfo) {
LauncherAppWidgetProviderInfo widgetInfo = (LauncherAppWidgetProviderInfo) o;

// Ensure that all widgets we show can be added on a workspace of this size
int minSpanX = Math.min(widgetInfo.spanX, widgetInfo.minSpanX);
int minSpanY = Math.min(widgetInfo.spanY, widgetInfo.minSpanY);
if (minSpanX <= (int) idp.numColumns &&
minSpanY <= (int) idp.numRows) {
componentName = widgetInfo.provider;
packageName = widgetInfo.provider.getPackageName();
userHandle = mAppWidgetMgr.getUser(widgetInfo);
} else {
if (DEBUG) {
Log.d(TAG, String.format(
"Widget %s : (%d X %d) can't fit on this device",
widgetInfo.provider, minSpanX, minSpanY));
}
continue;
}
} else if (o instanceof ResolveInfo) {
ResolveInfo resolveInfo = (ResolveInfo) o;
componentName = new ComponentName(resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name);
packageName = resolveInfo.activityInfo.packageName;
userHandle = UserHandleCompat.myUserHandle();
}

if (componentName == null || userHandle == null) {
Log.e(TAG, String.format("Widget cannot be set for %s.", o.getClass().toString()));
continue;
}

if (mAppFilter != null && !mAppFilter.shouldShowApp(componentName)) {
if (DEBUG) {
Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
packageName));
}
continue;
}

PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
ArrayList<Object> widgetsShortcutsList = mWidgetsList.get(pInfo);
if (widgetsShortcutsList != null) {
widgetsShortcutsList.add(o);
} else {
widgetsShortcutsList = new ArrayList<>();
widgetsShortcutsList.add(o);
pInfo = new PackageItemInfo(packageName);
mIconCache.getTitleAndIconForApp(packageName, userHandle,
true /* userLowResIcon */, pInfo);
pInfo.titleSectionName = mIndexer.computeSectionName(pInfo.title);
mWidgetsList.put(pInfo, widgetsShortcutsList);
tmpPackageItemInfos.put(packageName, pInfo);
mPackageItemInfos.add(pInfo);
}
}

// sort.
Collections.sort(mPackageItemInfos, mAppNameComparator);
for (PackageItemInfo p: mPackageItemInfos) {
Collections.sort(mWidgetsList.get(p), mWidgetAndShortcutNameComparator);
}
}

在for循环里添加了一个判断,如果mAppFilter.shouldShowApp返回false则continue跳过处理即不添加到widgetsShortcutsList里。从而实现了在小部件列表里不显示

那如果我们只想单独隐藏某个应用或者某个应用的某一个widget该怎么实现呢,其实很简单,仿照AppFilter写一个WidgetFilter,然后在setWidgetsAndShortcuts里调用AppFilter的下面在加一个判断就好了:

1
2
3
4
5
6
7
8
9
10
11
if (mAppFilter != null && !mAppFilter.shouldShowApp(componentName)) {
if (DEBUG) {
Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
packageName));
}
continue;
}

if (mWidgetFilter != null && !mWidgetFilter.shouldShowAppWidget(componentName)) {
continue;
}

这样就实现了对单独对widget的隐藏。

坚持原创技术分享,您的支持将鼓励我继续创作!