Launcher3显示指定的应用

公司的需求:
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,在进入设置-->无障碍-->显示应用到桌面里,新应用的勾选没有打算,但是缺显示在桌面上
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容