Launcher学习(一):未接电话及未读短信监听提醒

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/china_41/article/details/51012730

先来张我修改好的效果图:


再整理下思路及需要我们解决的难点:

1.如何定位到Launcher界面
2.如何监听未接电话及未读短信
3.如何动态的注册及注销数据库变化的监听
4.如何根据监听查询数据库未读短信及未接电话
5.查询到未接电话及未接短信后怎么通知Launcher界面
6.接到通知后,Launcher如何查找到电话及短信应用
7.找到对应的应用后,如何将对应图标上显示红点

思考过上面这些问题之后,那么我们来尝试解决这些问题吧!

1.如何定位到Launcher界面

大家eclipse及系统源码比较熟悉的人,可能很快就找到了。那么在这里我推荐一种方法,Ctrl+Shift+R 进行快捷搜索,可以很快定位到应用的路径下,如下图。

2.如何监听未接电话及未读短信

这里使用的是ContentObserver进行监听数据库变化,具体代码实现如下:
 public class ContactsContentObserver extends ContentObserver{
        
        public ContactsContentObserver() {
            super(new Handler());
        }
        
        @Override
        public void onChange(boolean selfChange) {
            Launcher.this.mHandler.removeMessages(UPDATE_DISMISS_CALL_MSG); 
            Launcher.this.mHandler.sendEmptyMessage(UPDATE_DISMISS_CALL_MSG);
            super.onChange(selfChange);
        }
    }
     
    private class SmsMmsContentObserver extends ContentObserver {
        public SmsMmsContentObserver() {
            super(new Handler());
        }

        @Override
        public void onChange(boolean selfChange) {
            Launcher.this.mHandler.removeMessages(UPDATE_DISMISS_MSM_MSG); 
            Launcher.this.mHandler.sendEmptyMessage(UPDATE_DISMISS_MSM_MSG);
            super.onChange(selfChange);
        }
    }
可以从上面的代码中看出,当数据库发生变化时,会主动发出mHandler消息。

3.如何动态的注册及注销数据库变化的监听

要想上面我们实现的方法生效,还必须对这2个ContentObserver进行注册。
首先在开头先new出来2个我们实现类的对象。
  private final ContentObserver mContactsObserver = new ContactsContentObserver();
  private final ContentObserver mSmsMmsObserver = new SmsMmsContentObserver();
如何在Launcher代码中搜索registerContentObservers();这个方法,可以看到他在oncrete()方法中被调用,也就是当launcher创建的时候,进行注册。实现代码如下:
 private void registerContentObservers() {
        ContentResolver resolver = getContentResolver();
        resolver.registerContentObserver(LauncherProvider.CONTENT_APPWIDGET_RESET_URI,
                true, mWidgetObserver);
       <span style="color:#ff0000;"> resolver.registerContentObserver(Calls.CONTENT_URI, true, mContactsObserver);
        resolver.registerContentObserver(Uri.parse("content://sms"), true,
                mSmsMmsObserver);
        resolver.registerContentObserver(Uri.parse("content://mms/inbox/"), true,</span>
                mSmsMmsObserver);
        resolver.registerContentObserver(mLimitUri, true, mLimitObserver);
    }
这里比较注意的是,我们手机正常是分短信及彩信的,所以注册的时候是需要2个地方都要注册的。
到这步,我们监听已经完成了,当数据库数据发生变化时,我们第二步实现的类中,就会发生handler信息。我们在看看如何注销监听吧。
按正常思路来说,当界面注销时,我们的监听也要注销,所以我们直接定位到onDestroy()方法中,果然找到了系统源码一些其他监听的注销代码,我们也加上我们的代码吧。
 getContentResolver().unregisterContentObserver(mWidgetObserver);
 <span style="color:#ff0000;">getContentResolver().unregisterContentObserver(mContactsObserver);
 getContentResolver().unregisterContentObserver(mSmsMmsObserver);</span>
 getContentResolver().unregisterContentObserver(mLimitObserver);
 unregisterReceiver(mCloseSystemDialogsReceiver);

4.如何根据监听查询数据库未读短信及未接电话

再来看看我们第二步mhandler发生的信息。未接电话的代码:
else if (msg.what == UPDATE_DISMISS_CALL_MSG) {

                final AsyncTask<Void, Void, Integer> qureyTask = new AsyncTask<Void, Void, Integer>() {
                    @Override
                    protected Integer doInBackground(Void... unused) {
                        Integer result = Integer.valueOf(0);
                        Cursor cur = null;
                        try {
                            cur = getContentResolver().query(Calls.CONTENT_URI, null,
                                    "type = 3 and new = 1", null, null);
                            if (null != cur) {
                                result = Integer.valueOf(cur.getCount());
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            if (cur != null) {
                                cur.close();
                            }
                        }
                        return result;
                    }

                    @Override
                    protected void onPostExecute(Integer result) {
                        int value = result.intValue();
                        String strValue = "";
                        if (value > 0) {
                            if (value > 99)
                                value = 99;
                            strValue = String.valueOf(value);
                        }
                        updateAppSubscript("com.android.dialer",
                              "com.android.dialer.DialtactsActivity",
                              strValue);                     
                    }
                };
                qureyTask.execute();
            }
这里使用了AsyncTask异步类,在doInBackground()方法中,先进行了查询数据库的操作,将查询到的未接电话数量result传递到onPostExecute方法中,这个方法将数据重新整理,如果超过99就强制赋值为99,再传给updateAppSubscript()这个方法。
未读短信部分代码:
else if (msg.what == UPDATE_DISMISS_MSM_MSG) {
                final AsyncTask<Void, Void, Integer> qureyTask = new AsyncTask<Void, Void, Integer>() {
                    @Override
                    protected Integer doInBackground(Void... unused) {
                        int result = Integer.valueOf(0);
                        result = getSmsMmsCount();
                        return result;
                    }

                    @Override
                    protected void onPostExecute(Integer result) {
                        int value = result.intValue();
                        String strValue = "";
                        if (value > 0) {
                            if (value > 99)
                                value = 99;
                            strValue = String.valueOf(value);
                        }
                        updateAppSubscript("com.android.mms",
                                "com.android.mms.ui.ModeChooser",
                                strValue);// com.android.mms.ui.ConversationList
                        System.out.println("strValue"+strValue);
                    }
                };
                qureyTask.execute();
            }
        }
 private int getSmsMmsCount(){
        int missSmsCount = 0;
        Cursor cursorSMS = null;
        Cursor cursorMMS = null;
        try {
            cursorSMS = getContentResolver().query(
                    Uri.parse("content://sms"), null, "(read=0 and type=1)",
                    null, null);
            cursorMMS = getContentResolver().query(
                    Uri.parse("content://mms/inbox/"), null,
                    "(read=0 and (m_type = 130 or m_type = 132))", null, null);
        } catch (Exception e) {
            return missSmsCount;
        }
        if (cursorSMS != null) {
            missSmsCount = cursorSMS.getCount();
            cursorSMS.close();
        }
        if (cursorMMS != null) {
            missSmsCount = missSmsCount + cursorMMS.getCount();
            cursorMMS.close();
        }

        return missSmsCount;
    }
因为我们需要获取的数是短信及彩信之和,所以我这里是重新定义了一个方法,具体代码就是和电话部分一样了。

5.查询到未接电话及未接短信后怎么通知Launcher界面

第四步将查询到的数据传入updateAppSubscript()方法中,用次方法来查找Launcher界面的应用是否为短信或者电话,代码如下:
public void updateAppSubscript(String packageName, String className, String script) {
        System.out.println("mWorkspace"+mWorkspace);
        if (mWorkspace == null || mScriptShortcutMap == null || mScriptShortcutMap.size() <= 0)
            return;
        if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className))
            return;

        ComponentName component = new ComponentName(packageName, className);
        ShrotcutSubscript shortcutScript = mScriptShortcutMap.get(component);
        if (shortcutScript == null)
            return;
        shortcutScript.setSubscript(script);
        System.out.println("script"+script);
        Log.d(TAG, "updateAppSubscript ComponentName: " + component + ", subscript: " + script);
        if (mWorkspace != null) {
            ArrayList<CellLayout> layouts = mWorkspace.getWorkspaceAndHotseatCellLayouts();
            for (CellLayout group : layouts) {
                ViewGroup cellGroup = group.getShortcutsAndWidgets();
                int count = cellGroup.getChildCount();
                for (int i = 0; i < count; i++) {
                    View child = cellGroup.getChildAt(i);
                    System.out.println("count"+count);
                    updateViewSubscript(child);
                }
            }
        }
        if (mAppsCustomizeContent != null) {
            int count = mAppsCustomizeContent.getChildCount();
            for (int i = 0; i < count; i++) {
                View pagedView = mAppsCustomizeContent.getChildAt(i);
                if (pagedView instanceof PagedViewCellLayout) {
                    PagedViewCellLayout cellLayout = (PagedViewCellLayout) pagedView;
                    int cellCount = cellLayout.getPageChildCount();
                    for (int j = 0; j < cellCount; j++) {
                        View child = cellLayout.getChildOnPageAt(j);
                        updateViewSubscript(child);
                        System.out.println("pagedView"+count);
                    }
                }
            }
        }
    }
该方面首先传入了包名及类名,用以查找Workspace(我们手机应用都是显示在Workspace中的)中的应用是否是短信或者电话。再将包名及类名new出来一个component对象,再通过 ShrotcutSubscript shortcutScript = mScriptShortcutMap.get(component);获得一个shortcutScript 对象,mScriptShortcutMap这个是一个hashmap,源码中是这样定义的。
private HashMap<ComponentName, ShrotcutSubscript> mScriptShortcutMap;

并在finishBindingItems中进行初始化。

 void initShortcutSubscript() {
        mScriptShortcutMap = new HashMap<ComponentName, ShrotcutSubscript>();
        ComponentName msg = new ComponentName("com.android.mms",
                "com.android.mms.ui.ModeChooser");
        ComponentName call = new ComponentName("com.android.dialer",
                "com.android.dialer.DialtactsActivity");
        mScriptShortcutMap.put(call, new ShrotcutSubscript(call));
        mScriptShortcutMap.put(msg, new ShrotcutSubscript(msg));
        mHandler.sendEmptyMessage(UPDATE_DISMISS_MSM_MSG);
        mHandler.sendEmptyMessage(UPDATE_DISMISS_CALL_MSG);
    }

6.接到通知后,Launcher如何查找到电话及短信应用

我们再来看看ShrotcutSubscript 这个类是什么东东。

public class ShrotcutSubscript {

        private String mSubscript;
        private long mFilterContainer;
        private ComponentName mComponentName;
        private Intent mIntent;
        // private Drawable mOriginalIcon;
        Bitmap mIcon;

        public ShrotcutSubscript(ComponentName componentName) {
            this(componentName, 0);
        }

        public ShrotcutSubscript(ComponentName componentName, int container) {
            mFilterContainer = container;
            mComponentName = componentName;
            mIntent = Intent.makeMainActivity(mComponentName);
            mIcon = mIconCache.getIcon(mIntent);

            final PackageManager manager = getPackageManager();
            final ResolveInfo resolveInfo = manager.resolveActivity(mIntent, 0);
            // mOriginalIcon = mIconCache.getFullResIcon(resolveInfo);
        }

        public String getSubscript() {
            return mSubscript;
        }

        public void setSubscript(String sbuscript) {
            mSubscript = sbuscript;
            // if(mOriginalIcon == null) return;
            Bitmap originalIcon = mIconCache.getIcon(mIntent);
            Bitmap icon = Utilities.createSubscriptBitmap(Launcher.this, originalIcon, mSubscript);
            if (icon == null)
                return;
            mIcon = icon;
        }

        public void updateShortcut(View view) {
            Object obj = view.getTag();
            Log.d(TAG, "updateShortcut subscript:" + mSubscript);
            if (mIcon == null) {
                return;
            }
            if (view instanceof BubbleTextView) {
                Log.d(TAG, "updateShortcut BubbleTextView");
                ShortcutInfo info = (ShortcutInfo) obj;
                info.setIcon(mIcon);
                BubbleTextView textView = (BubbleTextView) view;
                textView.setCompoundDrawablesWithIntrinsicBounds(null,
                        new FastBitmapDrawable(mIcon),
                        null, null);
                textView.invalidate();
            } else if (view instanceof PagedViewIcon) {
                AppInfo info = (AppInfo) obj;
                info.iconBitmap = mIcon;
                PagedViewIcon textView = (PagedViewIcon) view;
                textView.setCompoundDrawablesWithIntrinsicBounds(null,
                        new FastBitmapDrawable(mIcon),
                        null, null);
                textView.invalidate();
            }
        }
    }
首先,这个类是根据我们传入的包名及类名,得到了应用的各种信息,同时setSubscript()方法中获取的icon进行透析并将我们前面获取到的数据显示到icon上,形成新的icon,那么我们可以理解ShrotcutSubscript shortcutScript = mScriptShortcutMap.get(component);是为了获取应用信息的对象,如何在setSubscript()方法中传入需要显示的数据。再继续分析,那么设置好了之后,怎么判断这个应用是显示在哪里的呢。这里就是通过 mWorkspace.getWorkspaceAndHotseatCellLayouts();来获取用户界面,这里介绍下手机界面一般分为2种layout,即我们可以左右滑动的界面,还有就是下面4个一般用来放置电话短信的界面。

这里对layout进行了循环查找,并把每个celllayout通过updateViewSubscript(child);方法进行了分析。

public void updateViewSubscript(View view) {
        if (view == null)
            return;
        if (view instanceof BubbleTextView) {
            System.out.println("BubbleTextView");
            updateShrotcutSubscript(view);
        } else if (view instanceof FolderIcon) {
            System.out.println("FolderIcon");
            updateFolderSubscriptIcon(view);
        } else if (view instanceof PagedViewIcon) {
            System.out.println("PagedViewIcon");
            updatePagedviewIconSubscript(view);
        } else {
            Log.d(TAG, "updateViewSubscript " + view);
        }
    }
这里celllayout分成三种,普通的应用一般是BubbleTextView,文件夹是FolderIcon,PagedViewIcon至今我还没弄清楚是什么,我猜测是类似始终或者是快捷设置的应用。

这里如果我们把短信放到桌面上那么将走updateShrotcutSubscript(view);方法。

  public void updateShrotcutSubscript(View view) {
        if (view == null)
            return;
        System.out.println("updateShrotcutSubscript1");
//        if (!(view instanceof BubbleTextView))
//            return;
        System.out.println("updateShrotcutSubscript12");
        if (mScriptShortcutMap == null || mScriptShortcutMap.size() <= 0)
            return;
        System.out.println("updateShrotcutSubscript13");
        ShortcutInfo info = (ShortcutInfo) view.getTag();
        if (info == null || info.intent == null || info.intent.getComponent() == null)
            return;
        System.out.println("updateShrotcutSubscript14");
        ComponentName component = info.intent.getComponent();
        ShrotcutSubscript shortcutScript = mScriptShortcutMap.get(component);
        if (shortcutScript != null) {
            Log.d(TAG, "updateShrotcutSubscript " + component + ", container ");
            shortcutScript.updateShortcut(view);
            System.out.println("5");
        }
    }
如果将应用放入文件夹中,则会进行下面的代码。

   public void updateFolderSubscriptIcon(View folderView) {
        if (folderView == null || !(folderView instanceof FolderIcon))
            return;
        Log.d(TAG, "updateFolderSubscriptIcon !!!");

        FolderInfo fInfo = ((FolderIcon) folderView).getFolderInfo();
        BubbleTextView textview = null;
        System.out.println(fInfo.toString());
        Folder folder = ((FolderIcon) folderView).getFolder();
//        Folder folder = mWorkspace.getOpenFolder();
//        Folder folder = new Folder(folderView.getContext(),null);
        View view = null;
        ShortcutInfo info = null;
        System.out.println("folder.getChildCount()="+folder.getItemCount());
        for (int i = 0; i < folder.getItemCount(); i++) {
            view = folder.getItemAt(i);
            System.out.println("view="+view);
            if (view instanceof BubbleTextView) {
                System.out.println("BubbleTextView");
                updateShrotcutSubscript(view);
            }
        }   
        folderView.invalidate();
    }
同样是Pagedview,则进行下面的代码。

 public void updatePagedviewIconSubscript(View view) {
        if (view == null)
            return;
        if (!(view instanceof PagedViewIcon))
            return;
        if (mScriptShortcutMap == null || mScriptShortcutMap.size() <= 0)
            return;

        AppInfo info = (AppInfo) view.getTag();
        if (info == null || info.componentName == null)
            return;
         
        ShrotcutSubscript shortcutScript = mScriptShortcutMap.get(info.componentName);
        if (shortcutScript != null) {
            Log.d(TAG, "updatePagedviewIconSubscript2 " + info.componentName);
            System.out.println("updatePagedviewIconSubscript");
            shortcutScript.updateShortcut(view);
        }
    }

7.找到对应的应用后,如何将对应图标上显示红点

其实这一步应该和第6步放在一起看,因为ShrotcutSubscript类中,不仅封装了设置新icon的方法,同时也封装了更新应用icon的方法shortcutScript.updateShortcut(view);

代码分析并不难,大家自己研究研究吧。

8.讲讲我遇到的一些问题

1.只监听了短信的数据库,而没有监听彩信的收件箱
2.只有监听到新的短信到来时,才能发送handler,而我点击未读短信后,并不会发送handler,很奇怪的一个问题,至今也没有想明白,我的解决方法是在onResume()方法中添加了handler,每次launcher获取焦点的时候发送。
3.获取文件夹中的celllayou时,一定不要用folder.getChildAt()获取文件夹里的控件,而要用folder.getItemAt(i)获取。































展开阅读全文

没有更多推荐了,返回首页