美文网首页android系统应用开发
Launcher3显示指定的应用

Launcher3显示指定的应用

作者: Ayugone | 来源:发表于2018-10-26 10:13 被阅读0次

    公司的需求:
    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,在进入设置-->无障碍-->显示应用到桌面里,新应用的勾选没有打算,但是缺显示在桌面上

    相关文章

      网友评论

        本文标题:Launcher3显示指定的应用

        本文链接:https://www.haomeiwen.com/subject/ikqktqtx.html