公司的需求:
1.要求在系统设置里面增加一项,可以选择哪些app展示在桌面上,并且系统密码开关打开时不可进入修改,关闭时才可进入修改。
2.桌面默认不显示任何app,点击进入第二层的时候,默认只有一个设置按钮
3.系统为Android6.0,修改的是Launcher3
效果图:系统设置-->无障碍-->显示桌面应用
Screenshot_20160101-012147.png Screenshot_20160101-012253.png Screenshot_20160101-010228.png Screenshot_20160101-010233.png Screenshot_20160101-010235.png
勾选了电话和计算器
Screenshot_20160101-012556.png
所有应用列表里展示了电话和计算器
Screenshot_20160101-012603.png
把电话和计算器拖动到桌面
Screenshot_20160101-012608.png Screenshot_20160101-012614.png
再进入设置里面,把电话和计算器的勾选去掉,回到桌面就会消失
Screenshot_20160101-012147.png Screenshot_20160101-012253.png
一,Launcher简介
Launcher3是分两层显示的,第一层就是开机启动和用户按Home键后显示的页面(桌面),第二层是用来展示系统中所有需要显示到Launcher上的应用(我们常说的抽屉)。当然,并非所有的Launcher都有两层结构,比如小米Launcher就只有一层结构,可根据实际需求进行设计。
20160701104615722.png二,开发步骤:
在Launcher3项目里的Launcher类是这个Activity是这个应用的主界面
第一步:
我们先把Hotseat里的除了mAllAppsButton这个进入所有应用类表里的按钮之外的其他四个隐藏掉。
在laucher.xml里面:
1540523647(1).png
要隐藏4个按钮,需要在Launcher3的xml文件夹中注释dw_phone_hotseat.xml里面的内容
1540524452(1).png
注释之后就不会展示了,这个dw_phone_hotseat.xml被default_workspace_5x5.xml,default_workspace_4x4.xml所引用,在InvariantDeviceProfile这个类里面被加载
第二步:开发控制显示在Launcher中的类:ShowAppInLauncherActivity,建议现在eclipse中先开发好布局和跟系统无关的业务逻辑,再copy到Launcher3项目下,要特别注意包名是否对得上。
1.现在系统设置--无障碍列表 中新增一个PreferenceScreen
1540524878(1).png
2.开发ShowAppInLauncherActivity,忽略报错,这是从系统拷出来的,报错正常。
1540524981(1).png
AppInfo类,isChecked主要是用来记录有没有勾选
1540525071(1).png
ShowAppInLauncherActivity类
1540525782(1).png
GetAllApp方法,主要是获取应用列表,并根据从Settings.Global里获取到系统保存的可显示的应用列表,通过对isCheck进行标记
1540525879.png
将保存的包名字符串转化为集合
1540531099(1).png适配器
1540531202(1).png 1540531260(1).png
这里checkbox和listView是有冲突的,所以使用onClickListener来处理,并且需要使用一个HashMap:isSelected来保存点击状态,只要点击了checkbox就会发送广播给Launcher,在Launcher的OnResume方法重新加载数据
1540531343(1).png 1540531518(1).png
这里初始化showMap,将所有app中标记了isChecked为true的保存到showMap里,可以根据选中状态来增删
1540531797(1).png
public class ShowAppInLauncherActivity extends Activity implements
OnItemClickListener {
private ArrayList<AppInfo> appList = new ArrayList<AppInfo>();
ListView listView;
AppInfo appInfo;
HashMap<String, String> showMap;
private ArrayList<String> showList = new ArrayList<String>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_app_in_launcher);
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
GetAllApp();
initShowMap();
initView();
}
private void initShowMap() {
showMap = new HashMap<String, String>();
for (AppInfo a : appList) {
if (a.isChecked())
showMap.put(a.getAppPackage(), a.getAppPackage());
}
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void initView() {
listView = (ListView) findViewById(R.id.listVieiw);
listView.setAdapter(new MyAdatper(this));
listView.setOnItemClickListener(this);
}
public ArrayList<String> getShowAppArrays(String showAppInLauncher) {
return new ArrayList<String>(
Arrays.asList(showAppInLauncher.split(",")));
}
private void GetAllApp() {
String showAppInLauncher = Settings.Global.getString(
ShowAppInLauncherActivity.this.getContentResolver(),
Settings.Global.SHOW_APP_IN_LAUNCHER);
showList = getShowAppArrays(showAppInLauncher);
PackageManager pm = this.getPackageManager();
Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> resolveInfos = pm
.queryIntentActivities(mainIntent, 0);
Collections.sort(resolveInfos,
new ResolveInfo.DisplayNameComparator(pm));
if (appList != null) {
appList.clear();
for (ResolveInfo reInfo : resolveInfos) {
String activityName = reInfo.activityInfo.name;
String pkgName = reInfo.activityInfo.packageName;
String appLabel = (String) reInfo.loadLabel(pm);
Drawable icon = reInfo.loadIcon(pm);
Intent launchIntent = new Intent();
launchIntent.setComponent(new ComponentName(pkgName,
activityName));
AppInfo appInfo = new AppInfo();
appInfo.setAppName(appLabel);
appInfo.setAppPackage(pkgName);
appInfo.setAppIcon(icon);
appInfo.setAppIntent(launchIntent);
if (showList.contains(pkgName)) {
// Toast.makeText(this, pkgName +"is true",
// Toast.LENGTH_SHORT).show();
appInfo.setChecked(true);
} else {
appInfo.setChecked(false);
}
appList.add(appInfo); // 娣诲姞鑷冲垪琛ㄤ腑
}
}
}
public String getShowAppInLauncherInfo() {
StringBuilder sb = new StringBuilder();
for (String value : showMap.values()) {
sb.append(value + ",");
}
return sb.toString();
}
class MyAdatper extends BaseAdapter {
private LayoutInflater inflater;
private AppInfo app;
private HashMap<Integer, Boolean> isSelected;
public MyAdatper(Context context) {
inflater = LayoutInflater.from(context);
isSelected = new HashMap<Integer, Boolean>();
initDate();
}
private void initDate() {
for (int i = 0; i < appList.size(); i++) {
getIsSelected().put(i, appList.get(i).isChecked());
}
}
@Override
public int getCount() {
return appList.size();
}
@Override
public Object getItem(int position) {
return appList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView,
ViewGroup parent) {
final ViewHolder viewHolder;
if (convertView == null) {
convertView = inflater.inflate(
R.layout.show_app_in_laucher_iteminfo, null);
viewHolder = new ViewHolder();
viewHolder.title = (TextView) convertView
.findViewById(R.id.itemName);
viewHolder.image = (ImageView) convertView
.findViewById(R.id.itemImage);
viewHolder.checkBox = (CheckBox) convertView
.findViewById(R.id.checkBox);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
app = (AppInfo) appList.get(position);
viewHolder.title.setText(appList.get(position).getAppName());
viewHolder.image.setImageDrawable(appList.get(position)
.getAppIcon());
viewHolder.checkBox.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (isSelected.get(position)) {
isSelected.put(position, false);
setIsSelected(isSelected);
showMap.remove(appList.get(position).getAppPackage());
} else {
isSelected.put(position, true);
setIsSelected(isSelected);
showMap.put(appList.get(position).getAppPackage(),
appList.get(position).getAppPackage());
}
Settings.Global.putString(
ShowAppInLauncherActivity.this.getContentResolver(),
Settings.Global.SHOW_APP_IN_LAUNCHER,
getShowAppInLauncherInfo());
Intent intent = new Intent();
intent.setAction("com.andorid.setting.action.isneedtoreload");
intent.putExtra("isNeedToReloadLauncher", true);
sendBroadcast(intent);
}
});
if ("com.android.settings".equals(appList.get(position)
.getAppPackage())) {
viewHolder.checkBox.setChecked(true);
viewHolder.checkBox.setEnabled(false);
} else {
viewHolder.checkBox.setChecked(getIsSelected().get(position));
viewHolder.checkBox.setEnabled(true);
}
return convertView;
}
public HashMap<Integer, Boolean> getIsSelected() {
return isSelected;
}
public void setIsSelected(HashMap<Integer, Boolean> isSelected) {
this.isSelected = isSelected;
}
}
class ViewHolder {
public TextView title;
public ImageView image;
public CheckBox checkBox;
}}
这里要说一下,Setting.Global
Settings.java的路径:Z:\JP762A_Proj\frameworks\base\core\java\android\provider\Settings
SettingsProvider 对数据进行了分类,分别是 Global、System、Secure 三种类型,它们的区别如下:
Global:所有的偏好设置对系统的所有用户公开,第三方APP有读没有写的权限;
System:包含各种各样的用户偏好系统设置;
Secure:安全性的用户偏好系统设置,第三方APP有读没有写的权限。
public static final class Global extends NameValueTable {
public static final String SYS_PASSWORD = "sys_password";
public static final String SHOW_APP_IN_LAUNCHER="show_app_in_launcher";
public static final String IS_NEED_TO_RELOAD_LAUNCHER="is_need_to_reload_launcher";}
在Global中配置字段,然后在Secure中添加进MOVED_TO_GLOBAL
public static final class Secure extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/secure");
// Populated lazily, guarded by class object:
private static final NameValueCache sNameValueCache = new NameValueCache(
SYS_PROP_SETTING_VERSION,
CONTENT_URI,
CALL_METHOD_GET_SECURE,
CALL_METHOD_PUT_SECURE);
private static ILockSettings sLockSettings = null;
private static boolean sIsSystemProcess;
private static final HashSet<String> MOVED_TO_LOCK_SETTINGS;
private static final HashSet<String> MOVED_TO_GLOBAL;
static {
MOVED_TO_GLOBAL.add(Settings.Global.SHOW_APP_IN_LAUNCHER);}
Z:\JP762A_Proj\frameworks\base\packages\SettingsProvider\src\com\android\providers\settings\DataBaseHelper
默认系统中只保留的图标为设置,com.android.settings
loadStringSetting(stmt, Settings.Global.SHOW_APP_IN_LAUNCHER,
R.string.def_show_app_in_launcher);
R.string.def_show_app_in_launcher在values/default.xml里
<string name="def_show_app_in_launcher" translatable="false">com.android.settings,</string>
3.现在说Launcher如何根据我们保存在数据库里的包名来过滤app的显示
Launcher的数据管理通过LauncherModel类来管理
LauncherModel类里有个LoaderTask,是用来加载数据的
image.png
public void run() {
synchronized (mLock) {
if (DEBUG_LOADERS) {
LauncherLog.d(TAG, "Set load task running flag >>>>, mIsLaunching = " +
",this = " + this);
}
if (mStopped) {
return;
}
mIsLoaderTaskRunning = true;
}
// Optimize for end-user experience: if the Launcher is up and // running with the
// All Apps interface in the foreground, load All Apps first. Otherwise, load the
// workspace first (default).
keep_running: {
if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
loadAndBindWorkspace();
if (mStopped) {
LauncherLog.i(TAG, "LoadTask break in the middle, this = " + this);
break keep_running;
}
waitForIdle();
// second step
if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
loadAndBindAllApps();
}
// Clear out this reference, otherwise we end up holding it until all of the
// callback runnables are done.
mContext = null;
synchronized (mLock) {
// If we are still the last one to be scheduled, remove ourselves.
if (mLoaderTask == this) {
mLoaderTask = null;
}
if (DEBUG_LOADERS) {
LauncherLog.d(TAG, "Reset load task running flag <<<<, this = " + this);
}
mIsLoaderTaskRunning = false;
mHasLoaderCompletedOnce = true;
}
}
在keep_running代码块中的loadAndBindAllApps方法
private void loadAndBindAllApps() {
if (LauncherLog.DEBUG_LOADER) {
LauncherLog.d(TAG, "loadAndBindAllApps: mAllAppsLoaded =" + mAllAppsLoaded
+ ", mStopped = " + mStopped + ", this = " + this);
}
if (!mAllAppsLoaded) {
loadAllApps();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
}
updateIconCache();
synchronized (LoaderTask.this) {
if (mStopped) {
return;
}
mAllAppsLoaded = true;
}
} else {
onlyBindAllApps();
}
}
1540536576(1).png
在loadAllApps()这个方法中进行所有应用列表的显示过滤
private void loadAllApps() {
final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final Callbacks oldCallbacks = mCallbacks.get();
if (oldCallbacks == null) {
// This launcher has exited and nobody bothered to tell us. Just bail.
Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
return;
}
final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
// Clear the list of apps
mBgAllAppsList.clear();
for (UserHandleCompat user : profiles) {
// Query for the set of apps
final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
if (DEBUG_LOADERS) {
Log.d(TAG, "getActivityList took "
+ (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
}
// Fail if we don't have any apps
// TODO: Fix this. Only fail for the current user.
if (apps == null || apps.isEmpty()) {
return;
}
String showAppInLauncher = Settings.Global.getString(mApp.getContext().getContentResolver(), Settings.Global.SHOW_APP_IN_LAUNCHER);
ArrayList<String> list=getShowAppArrays(showAppInLauncher);
// Create the ApplicationInfos
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfoCompat app = apps.get(i);
// 这里表示只有存在数据库里面的包名才可以加入到mBgAllAppsList中
if(list.contains(app.getComponentName().getPackageName())){
mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
}
}
final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
if (heuristic != null) {
final Runnable r = new Runnable() {
@Override
public void run() {
heuristic.processUserApps(apps);
}
};
runOnMainThread(new Runnable() {
@Override
public void run() {
// Check isLoadingWorkspace on the UI thread, as it is updated on
// the UI thread.
if (mIsLoadingAndBindingWorkspace) {
synchronized (mBindCompleteRunnables) {
mBindCompleteRunnables.add(r);
}
} else {
runOnWorkerThread(r);
}
}
});
}
}
// Huh? Shouldn't this be inside the Runnable below?
final ArrayList<AppInfo> added = mBgAllAppsList.added;
mBgAllAppsList.added = new ArrayList<AppInfo>();
// Post callback on main thread
mHandler.post(new Runnable() {
public void run() {
final long bindTime = SystemClock.uptimeMillis();
final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
if (callbacks != null) {
callbacks.bindAllApplications(added);
if (DEBUG_LOADERS) {
Log.d(TAG, "bound " + added.size() + " apps in "
+ (SystemClock.uptimeMillis() - bindTime) + "ms");
}
} else {
Log.i(TAG, "not binding apps: no Launcher activity");
}
}
});
// Cleanup any data stored for a deleted user.
ManagedProfileHeuristic.processAllUsers(profiles, mContext);
loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
if (DEBUG_LOADERS) {
Log.d(TAG, "Icons processed in "
+ (SystemClock.uptimeMillis() - loadTime) + "ms");
}
}
public void dumpState() {
synchronized (sBgLock) {
Log.d(TAG, "mLoaderTask.mContext=" + mContext);
Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
}
}
}
至此,我们只解决了所有应用列表里面的应用展示,还需要解决在workspace里面的shortcutInfo和hotseat里面的快捷图标的展示,这些同样在LauncherModel类中处理
image.png那么如何在设置中修改完之后,在Launcher中就马上有修改效果?
在onResume中进行判断
image.png
必须调用mModel.resetLoaderState(true,false);方法,再调用mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);才有效果,这里的mAllAppsLoaded和上面LaucherModel中loadAllApps()方法中有用到。
image.png
在Launcher中要注册一个广播接收器,接受Settings发出的广播,来给mNeedToReloadLauncher赋值
image.png image.png
到这里基本上就完成了,但是第一次刷机的时候,除了显示了设置之外,还额外多了通讯录和相机的图标,除了这里还有地方添加了图标
image.png
在PackageUpdatedTask的case OP_UPDATE中同样要进行过滤
image.png
这样就完成了过滤了,但是新安装的app要保存到SettingProvider里的DateBaseHelper中的数据库去,不然新安装的app,在进入设置-->无障碍-->显示应用到桌面里,新应用的勾选没有打算,但是缺显示在桌面上
网友评论