Launcher3顯示指定的應(yīng)用

公司的需求:
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)用


Screenshot_20160101-012147.png
Screenshot_20160101-012253.png
Screenshot_20160101-010228.png
Screenshot_20160101-010233.png
Screenshot_20160101-010235.png

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


Screenshot_20160101-012556.png

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


Screenshot_20160101-012603.png

把電話和計(jì)算器拖動(dòng)到桌面
Screenshot_20160101-012608.png
Screenshot_20160101-012614.png

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


Screenshot_20160101-012147.png
Screenshot_20160101-012253.png

一,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ì)。

20160701104615722.png

二,開(kāi)發(fā)步驟:
在Launcher3項(xiàng)目里的Launcher類是這個(gè)Activity是這個(gè)應(yīng)用的主界面

第一步:
我們先把Hotseat里的除了mAllAppsButton這個(gè)進(jìn)入所有應(yīng)用類表里的按鈕之外的其他四個(gè)隱藏掉。
在laucher.xml里面:


1540523647(1).png

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


1540524452(1).png

注釋之后就不會(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


1540524878(1).png

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


1540524981(1).png

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


1540525071(1).png

ShowAppInLauncherActivity類
1540525782(1).png

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


1540525879.png

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

1540531099(1).png

適配器


1540531202(1).png
1540531260(1).png

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


1540531343(1).png
1540531518(1).png

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


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

這里要說(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ù)的


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()這個(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類中處理

image.png

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


image.png

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


image.png

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


image.png
image.png

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


image.png

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


image.png

這樣就完成了過(guò)濾了,但是新安裝的app要保存到SettingProvider里的DateBaseHelper中的數(shù)據(jù)庫(kù)去,不然新安裝的app,在進(jìn)入設(shè)置-->無(wú)障礙-->顯示應(yīng)用到桌面里,新應(yīng)用的勾選沒(méi)有打算,但是缺顯示在桌面上
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容