公司的需求:
1.要求在系統(tǒng)設(shè)置里面增加一項(xiàng),可以選擇哪些app展示在桌面上,并且系統(tǒng)密碼開(kāi)關(guān)打開(kāi)時(shí)不可進(jìn)入修改,關(guān)閉時(shí)才可進(jìn)入修改。
2.桌面默認(rèn)不顯示任何app,點(diǎn)擊進(jìn)入第二層的時(shí)候,默認(rèn)只有一個(gè)設(shè)置按鈕
3.系統(tǒng)為Android6.0,修改的是Launcher3
效果圖:系統(tǒng)設(shè)置-->無(wú)障礙-->顯示桌面應(yīng)用





勾選了電話和計(jì)算器

所有應(yīng)用列表里展示了電話和計(jì)算器

把電話和計(jì)算器拖動(dòng)到桌面


再進(jìn)入設(shè)置里面,把電話和計(jì)算器的勾選去掉,回到桌面就會(huì)消失


一,Launcher簡(jiǎn)介
Launcher3是分兩層顯示的,第一層就是開(kāi)機(jī)啟動(dòng)和用戶按Home鍵后顯示的頁(yè)面(桌面),第二層是用來(lái)展示系統(tǒng)中所有需要顯示到Launcher上的應(yīng)用(我們常說(shuō)的抽屜)。當(dāng)然,并非所有的Launcher都有兩層結(jié)構(gòu),比如小米Launcher就只有一層結(jié)構(gòu),可根據(jù)實(shí)際需求進(jìn)行設(shè)計(jì)。

二,開(kāi)發(fā)步驟:
在Launcher3項(xiàng)目里的Launcher類是這個(gè)Activity是這個(gè)應(yīng)用的主界面
第一步:
我們先把Hotseat里的除了mAllAppsButton這個(gè)進(jìn)入所有應(yīng)用類表里的按鈕之外的其他四個(gè)隱藏掉。
在laucher.xml里面:

要隱藏4個(gè)按鈕,需要在Launcher3的xml文件夾中注釋dw_phone_hotseat.xml里面的內(nèi)容

注釋之后就不會(huì)展示了,這個(gè)dw_phone_hotseat.xml被default_workspace_5x5.xml,default_workspace_4x4.xml所引用,在InvariantDeviceProfile這個(gè)類里面被加載
第二步:開(kāi)發(fā)控制顯示在Launcher中的類:ShowAppInLauncherActivity,建議現(xiàn)在eclipse中先開(kāi)發(fā)好布局和跟系統(tǒng)無(wú)關(guān)的業(yè)務(wù)邏輯,再copy到Launcher3項(xiàng)目下,要特別注意包名是否對(duì)得上。
1.現(xiàn)在系統(tǒng)設(shè)置--無(wú)障礙列表 中新增一個(gè)PreferenceScreen

2.開(kāi)發(fā)ShowAppInLauncherActivity,忽略報(bào)錯(cuò),這是從系統(tǒng)拷出來(lái)的,報(bào)錯(cuò)正常。

AppInfo類,isChecked主要是用來(lái)記錄有沒(méi)有勾選

ShowAppInLauncherActivity類

GetAllApp方法,主要是獲取應(yīng)用列表,并根據(jù)從Settings.Global里獲取到系統(tǒng)保存的可顯示的應(yīng)用列表,通過(guò)對(duì)isCheck進(jìn)行標(biāo)記

將保存的包名字符串轉(zhuǎn)化為集合

適配器


這里checkbox和listView是有沖突的,所以使用onClickListener來(lái)處理,并且需要使用一個(gè)HashMap:isSelected來(lái)保存點(diǎn)擊狀態(tài),只要點(diǎn)擊了checkbox就會(huì)發(fā)送廣播給Launcher,在Launcher的OnResume方法重新加載數(shù)據(jù)


這里初始化showMap,將所有app中標(biāo)記了isChecked為true的保存到showMap里,可以根據(jù)選中狀態(tài)來(lái)增刪

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;
}}
這里要說(shuō)一下,Setting.Global
Settings.java的路徑:Z:\JP762A_Proj\frameworks\base\core\java\android\provider\Settings
SettingsProvider 對(duì)數(shù)據(jù)進(jìn)行了分類,分別是 Global、System、Secure 三種類型,它們的區(qū)別如下:
Global:所有的偏好設(shè)置對(duì)系統(tǒng)的所有用戶公開(kāi),第三方APP有讀沒(méi)有寫(xiě)的權(quán)限;
System:包含各種各樣的用戶偏好系統(tǒng)設(shè)置;
Secure:安全性的用戶偏好系統(tǒng)設(shè)置,第三方APP有讀沒(méi)有寫(xiě)的權(quán)限。
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中添加進(jìn)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
默認(rèn)系統(tǒng)中只保留的圖標(biāo)為設(shè)置,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.現(xiàn)在說(shuō)Launcher如何根據(jù)我們保存在數(shù)據(jù)庫(kù)里的包名來(lái)過(guò)濾app的顯示
Launcher的數(shù)據(jù)管理通過(guò)LauncherModel類來(lái)管理
LauncherModel類里有個(gè)LoaderTask,是用來(lái)加載數(shù)據(jù)的

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();
}
}

在loadAllApps()這個(gè)方法中進(jìn)行所有應(yīng)用列表的顯示過(guò)濾
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);
// 這里表示只有存在數(shù)據(jù)庫(kù)里面的包名才可以加入到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());
}
}
}
至此,我們只解決了所有應(yīng)用列表里面的應(yīng)用展示,還需要解決在workspace里面的shortcutInfo和hotseat里面的快捷圖標(biāo)的展示,這些同樣在LauncherModel類中處理

那么如何在設(shè)置中修改完之后,在Launcher中就馬上有修改效果?
在onResume中進(jìn)行判斷

必須調(diào)用mModel.resetLoaderState(true,false);方法,再調(diào)用mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);才有效果,這里的mAllAppsLoaded和上面LaucherModel中l(wèi)oadAllApps()方法中有用到。

在Launcher中要注冊(cè)一個(gè)廣播接收器,接受Settings發(fā)出的廣播,來(lái)給mNeedToReloadLauncher賦值


到這里基本上就完成了,但是第一次刷機(jī)的時(shí)候,除了顯示了設(shè)置之外,還額外多了通訊錄和相機(jī)的圖標(biāo),除了這里還有地方添加了圖標(biāo)

在PackageUpdatedTask的case OP_UPDATE中同樣要進(jìn)行過(guò)濾

這樣就完成了過(guò)濾了,但是新安裝的app要保存到SettingProvider里的DateBaseHelper中的數(shù)據(jù)庫(kù)去,不然新安裝的app,在進(jìn)入設(shè)置-->無(wú)障礙-->顯示應(yīng)用到桌面里,新應(yīng)用的勾選沒(méi)有打算,但是缺顯示在桌面上