Android綁定系統圖標,類似各種電話本的綁定圖標功能

這是一篇我個人在EOE發的帖子《綁定系統圖標,類似各種電話本的綁定圖標功能》,由於eoe的blog系統實在是無言以對,我就把eoe上面的帖子以及blog是都轉到csdn上來,原帖地址:http://www.eoeandroid.com/thread-542422-1-1.html


先說下背景把,之前要做一個融合的Contacts+Mms+ Dialer的一個項目,這個項目最初是要往第三方方面發展,然後要做一個功能:點擊系統的聯繫人,信息,撥號的icon然後把我們的app啓動起來,然後切換到相應的tab(做了類似功能的目前市面的app有:微信電話本,有你短信,觸寶號碼助手,360電話本,上面幾個都是我自己體驗並反編譯過他們的apk的,看的code太多了,就不貼截圖了)


這個功能呢,其實非常流氓,我也是通過看了一篇文章(文章地址:http://imid.me/blog/2013/08/16/weixin-phonebook-case-study/)才知道他們是具體怎麼做了,花了3天時間把基本功能做出來了,後面斷斷續續的優化功能也有一段時間
現在的情況是,我們的產品要進我們的rom了,進rom的話,我們就不需要這個功能了,所以把我之前研究的時候對整個流程的分析和我自己實現的demo分享出來

但是,希望大家不要利用這個去做一些不好的事情


通過上面那篇文章的分析,主要的做法就是利用WindowManager在整個屏幕窗口上添加一個透明且優先級比較高的窗口(是一個自定義View),然後監控屏幕的touch,用戶touch一次就去檢測一下系統目前運行的所有app,在RunningTask裏看棧頂的app是不是我們要綁定的app,如果是就啓動我們的app,如果不是就不用管了,而判斷是不是我們要綁定的app,是通過         
RunningTaskInfo runningTaskInfo = localObject.get(0);
ComponentName topActivity = runningTaskInfo.topActivity;
String topActivityClassName = topActivity.getClassName();
通過topActivity的類名,我們在啓動這個監控的時候會獲取一下我們需要的綁定的app的集合,然後在這個集合中過濾
基本思路就是這樣那下面就開始具體實現:(另外本帖主要分析微信電話本{以下稱wxdhb}的一些邏輯流程和我自己的實現,其他的app我也反編譯過,基本差不多的思路)
1、首先UI上有三個開關

開關的作用是發送自定義的廣播,接收到廣播之後來初始化一些數據,然後開啓service,然後添加透明窗口(其實可以直接添加透明窗口,wxdhb中好像就是直接搞的),(demo裏有些地方code寫的有些冗餘了,後期優化的時候直接在項目裏做的,就沒優化demo的code了,所以大家可以看着修改)//發送廣播

    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        SharedPreferences sp = getActivity().getSharedPreferences(BindActions.SCREEN_BIND_ACTIONS, Context.MODE_PRIVATE);
        Intent i = new Intent(BindActions.BIND_ACTION);
        switch (buttonView.getId()) {
            case R.id.sw_call:
                sp.edit().putBoolean(BindActions.BIND_ICON_ACTION_+"Call", buttonView.isChecked()).commit();
                break;
            case R.id.sw_contacts:
                sp.edit().putBoolean(BindActions.BIND_ICON_ACTION_+"Contacts", buttonView.isChecked()).commit();
                break;
            case R.id.sw_mms:
                sp.edit().putBoolean(BindActions.BIND_ICON_ACTION_+"Mms", buttonView.isChecked()).commit();
                break;
            default:
                break;
        }
        getActivity().sendBroadcast(i);
    }
public class ScreenStateReceiver extends BroadcastReceiver {
……
    @Override
    public void onReceive(Context context, Intent intent) {
//接受到上面發送的廣播,而且我們的不止是需要接收自己的廣播,還需要監聽系統的一些廣播,比如屏幕點亮和屏幕解鎖,且我們還要自啓動的時候就開始監聽了,所以需要接收多個action
        String intent_action = intent.getAction();
        if(BindActions.BIND_ACTION.equals(intent_action)){
            handleBindEvent(context, intent);
        } else if(Intent.ACTION_USER_PRESENT.equals(intent_action)){
            //Log.i("LM", "ACTION_USER_PRESENT");
            SystemClock.sleep(200);
            handleBindEvent(context, intent);
        } else if(Intent.ACTION_SCREEN_ON.equals(intent_action)){
            //Log.i("LM", "ACTION_SCREEN_ON");
            handleBindEvent(context, intent);
        } else if(Intent.ACTION_SCREEN_OFF.equals(intent_action)){
            //Log.i("LM", "ACTION_SCREEN_OFF");
        }else if(Intent.ACTION_BOOT_COMPLETED.equals(intent_action)){
            //Log.i("LM", "ACTION_BOOT_COMPLETED");
            handleBindEvent(context, intent);
        }


2、那麼下面service中,我們註冊廣播,和判斷是否需要添加透明窗口,然後來進行窗口的添加和移除

public class BindIconsService extends Service {
……
    @Override
    public void onCreate() {
        super.onCreate();
        receiver = new ScreenStateReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_USER_PRESENT);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        registerReceiver(receiver, filter );// 屏幕點亮的廣播,必須在code中註冊纔有效
    }
   @Override
    public void onDestroy() {
        super.onDestroy();
        if(receiver != null){
            unregisterReceiver(receiver);
        }
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        SharedPreferences sp = getSharedPreferences(BindActions.BIND_SERVICE_STATE,Context.MODE_PRIVATE);
        boolean b = sp.getBoolean(BIND_TAG, false);// 獲取是否需要綁定
        if (b) {
            AlertView.removeAlertWindow(this);//不需要綁定,刪除AlerView,並停止service
            stopSelf();
        } else {
            createView();
        }
        return START_STICKY;
    }
    /**
     * 添加透明窗口
     */
    private void createView() {
        SharedPreferences sp = getSharedPreferences(BindActions.SCREEN_BIND_ACTIONS, Context.MODE_PRIVATE);
        boolean _call = sp.getBoolean(BindActions.BIND_ICON_ACTION_+"Call", true);
        boolean _contacts = sp.getBoolean(BindActions.BIND_ICON_ACTION_+"Contacts", true);
        boolean _mms = sp.getBoolean(BindActions.BIND_ICON_ACTION_+"Mms", true);
        AlertView.addAlertWindow(this, _call, _contacts, _mms);//alertview就是那個自定義VIew
    }

3、關鍵的這個AlertView,這個view是透明的,懸浮在系統窗口之上,然後接收touch的時候調用一下我們自己的一個方法來進行一些判斷的邏輯,但是注意不要把touchevent攔截掉了(要不然用戶無法使用手機了,像我們開發的知道原因的話,還可以cmd下使用adb命令卸載掉,給小白用戶用,估計要哭了)

public class AlertView extends View {
……
   /**
     * 停止接受信息
     */
    private void stopHandleTouch(){
        if(this.eventHandler == null){
            return;
        }
        this.eventHandler.quit();
        this.eventHandler = null;
    }
    /**
     * 刪除透明窗口
     * @param context
     */
    public static void removeAlertWindow(Context context) {
        if (singleAlertView == null)
            return;
        if(singleAlertView != null){
            singleAlertView.stopHandleTouch();
        }
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        windowManager.removeView(singleAlertView);
        singleAlertView = null;
    }
    /**
     * 添加透明窗口
     * @param paramContext
     * @param is_bind_call
     * @param is_bind_contacts
     * @param is_bind_mms
     */
    public static void addAlertWindow(Context paramContext,boolean is_bind_call,boolean is_bind_contacts,boolean is_bind_mms) {
        if(singleAlertView != null){
            removeAlertWindow(paramContext);
        }
        singleAlertView = new AlertView(paramContext, is_bind_call, is_bind_contacts, is_bind_mms);
        addAlertView(paramContext);
    }
    /**
     * 開始接受屏幕touch事件的信息
     * @param paramContext
     */
    private void startHandleTouch(Context paramContext) {
        this.eventHandler = new TouchEventHandlerThread(this, paramContext);
        this.eventHandler.start();
        this.eventHandler.checkTop();
    }

    private static int exception_count = 0;
    /**
     * 添加窗口,<font color="#ff0000">這個方法的一些參數,我自己研究了很久,也去對應api了,上面幾個都可以對應出來具體參數,但**   是</font>localLayoutParams.flags <font color="#ff0000">這個參數,我真的不知道是個什麼鳥意思,但是這個值很關鍵,換其他值view沒效果(這段code我記得是從wxdhb和觸寶電話本里摳出來結合了一下吧)</font>
     * @param paramContext
     */
    private static void addAlertView(Context paramContext) {
        if (singleAlertView == null)
            return;
        WindowManager localWindowManager = (WindowManager) paramContext.getSystemService(Context.WINDOW_SERVICE);
        singleAlertView.setBackgroundColor(0);
        WindowManager.LayoutParams localLayoutParams = new WindowManager.LayoutParams();
        localLayoutParams.width = 1;
        localLayoutParams.height = 1;
        localLayoutParams.gravity = 51;
        localLayoutParams.x = 0;
        localLayoutParams.y = 0;
        localLayoutParams.format = 1;
        localLayoutParams.type = 2010;
        localLayoutParams.flags = 262152;
        try {
            localWindowManager.addView(singleAlertView, localLayoutParams);
            singleAlertView.startHandleTouch(paramContext);
            return;
        } catch (Exception localException1) {
            if(DEBUG){
                logMsg("Exception->"+localException1.getMessage());
            }
            removeAlertWindow(paramContext);
            if(exception_count > 5){
                if(DEBUG){
                    logMsg("can not add alertview ,exception:-->"+localException1.getMessage());
                }
                return;
            }
            addAlertView(paramContext);
            exception_count++;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if ((this.eventHandler != null) && (this.eventHandler.isAlive()))
            this.eventHandler.checkTop();
        logMsg("onTouchEvent---");
        return super.onTouchEvent(event);
    }

4、當然整個判斷的邏輯的過程要放在線程中執行,所以在這個類中還會用到TouchEventHandlerThread,該類中也有很變態的檢車棧頂的方法,做出來的效果,和其他的差不多,可能在app切換的時候做的還是有點差距,不過功能實現了,就是慢慢優化的事了

public class TouchEventHandlerThread extends HandlerThread {
……
   /**
     * 判斷是否有需要綁定
     * @return
     */
    private boolean hasBind() {
        if (alertView == null) {
            return false;
        } else {
            return alertView.isIs_bind_call() || alertView.isIs_bind_contacts()
                    || alertView.isIs_bind_mms();
        }
    }
    /**
     * 添加需要過濾的類名信息
     */
    public void initFilterActions() {
        Intent i_call = new Intent();
        i_call.setAction("android.intent.action.CALL_BUTTON");
        addFilterString(i_call, bind_set_call);
        Intent i_dial = new Intent();
        i_dial.setAction("android.intent.action.DIAL");
        addFilterString(i_dial, bind_set_call);
        Intent localIntent5 = new Intent();
        localIntent5.setAction("android.intent.action.CALL");
        addFilterString(localIntent5, bind_set_call);
        // s6
        bind_set_call.add("com.yulong.android.contacts.dial.DialActivity");
        // suning
        bind_set_call.add("com.android.contacts.activities.DialtactsActivity");

        Intent i_contents = new Intent();
        i_contents.setAction("android.intent.action.VIEW");
        i_contents.setType("vnd.android.cursor.dir/contact");
        addFilterString(i_contents, bind_set_contacts);
        Intent i_person = new Intent("android.intent.action.VIEW");
        i_person.setType("vnd.android.cursor.dir/person");
        addFilterString(i_person, bind_set_contacts);
        // meizu 
        bind_set_contacts.add("com.meizu.mzsnssyncservice.ui.SnsTabActivity");
        bind_set_contacts.add("com.sec.android.app.contacts.PhoneBookTopMenuActivity");
        // xiaomi
        bind_set_contacts.add("com.android.contacts.activities.TwelveKeyDialer");
        bind_set_contacts.add("com.android.mms.ui.MmsTabActivity");
        bind_set_contacts.add("com.android.contacts.activities.PeopleActivity");
        // s6
        bind_set_contacts.add("com.yulong.android.contacts.ui.main.ContactMainActivity");
        Intent i_mms = new Intent();
        i_mms.setAction("android.intent.action.MAIN");
        i_mms.setType("vnd.android.cursor.dir/mms");
        addFilterString(i_mms, bind_set_mms);
        Intent i_mms_2 = new Intent("android.intent.action.VIEW");
        i_mms_2.setType("vnd.android-dir/mms-sms");
        addFilterString(i_mms_2, bind_set_mms);

        bind_set_mms.add("com.android.mms.ui.ConversationList");
        bind_set_mms.add("com.android.mms");
        // huawei
        bind_set_mms.add("com.huawei.message");
        // sony
        bind_set_mms.add("com.sonyericsson.conversations");
        // motorola
        bind_set_mms.add("com.motorola.blur.conversations");
        bind_set_mms.add("com.android.mms.ui.SingleRecipientConversationActivity");
        bind_set_mms.add("com.android.mms.ui.NewMessagePopupActivity");
        // s6
        bind_set_mms.add("com.yulong.android.mms.ui.MmsMainListFormActivity");
        // 360
        bind_set_call.add("com.qihoo360.contacts.contacts");
        bind_set_contacts.add("com.qihoo360.contacts.safecontacts");
        bind_set_mms.add("com.qihoo360.contacts.mms");

        bind_set_mms.add("com.google.android.talk.SigningInActivity");
        bind_set_mms.add("com.google.android.apps.babel.fragments.SmsOobActivity");
    }
    /**
     * 根據Intent去系統中查詢可以啓動該類intent的程序信息
     * @param paramIntent
     * @param bind_set
     */
    private void addFilterString(Intent paramIntent, HashSet<String> bind_set) {
        try {
            ContextWrapper contextWrapper = (ContextWrapper) mContext;
            PackageManager packageManager = contextWrapper.getPackageManager();
            List<ResolveInfo> queryIntentActivities = packageManager.queryIntentActivities(paramIntent,PackageManager.MATCH_DEFAULT_ONLY);
            for (ResolveInfo resolveInfo : queryIntentActivities) {
                String packageName = resolveInfo.activityInfo.packageName;
                String name = resolveInfo.activityInfo.name;
                String targetActivity = resolveInfo.activityInfo.targetActivity;
                if (!TextUtils.isEmpty(name)) {
                    bind_set.add(name);
                }
                if (!TextUtils.isEmpty(targetActivity)) {
                    bind_set.add(targetActivity);
                }
            }
        } catch (Exception e) {
            logMsg("----------"+e.getMessage());
        }
    }

    boolean isSuccess = false;
    private int temp = 10;
    /**
     * 檢查棧頂
     */
    public void checkTop() {
        isSuccess = false;
        if (this.mHandler == null)
            return;
        this.mHandler.removeMessages(TouchHandler.MSG_WHAT_COMMENT);
        if(!hasBind()){
            return;
        }
        long i = 400L;
        while (true) {
            if(isSuccess){
                break;
            }
            if (i >= 600) {
                this.mHandler.sendEmptyMessageDelayed(TouchHandler.MSG_WHAT_COMMENT_FAILED, i);
                return;
            }
            this.mHandler.sendEmptyMessageDelayed(TouchHandler.MSG_WHAT_COMMENT, i);
            i += temp;
        }
    }
    /**
     * 檢查棧頂的Acitivity的類型(是否需要綁定)
     * @return
     */
    private int checkTopType() {
        SystemClock.sleep(200);
        int lanch_type = LANCH_TYPE_NONE;
        List<RunningTaskInfo> localObject = mActivityManager.getRunningTasks(1);
        if (localObject != null && localObject.size() > 0) {
            RunningTaskInfo runningTaskInfo = localObject.get(0);
            ComponentName topActivity = runningTaskInfo.topActivity;
            String topActivityClassName = topActivity.getClassName();
            if(mContext == null){
                return lanch_type;
            }
            if (mContext.getPackageName().equals(topActivity.getPackageName())) {
                isSuccess = true;
            }
            logMsg("topActivityClassName->"+topActivityClassName);
            if (bind_set_call.contains(topActivityClassName)) {
                if (alertView.isIs_bind_call()) {
                    lanch_type = LANCH_TYPE_CALL;
                }
            }else if (bind_set_contacts.contains(topActivityClassName)) {
                if (alertView.isIs_bind_contacts()) {
                    lanch_type = LANCH_TYPE_CONTACTS;
                }
            }else if (bind_set_mms.contains(topActivityClassName)) {
                if (alertView.isIs_bind_mms()) {
                    lanch_type = LANCH_TYPE_MMS;
                }
            }
        }
        return lanch_type;
    }
    /**
     * 幹掉其他程序,並啓動我們的app,<font color="#ff0000">這裏code寫了,但是沒有什麼效果,大家有好的殺其他app的辦法可以告訴我</font>
     */
    public void killTopAndExcuteMine() {
        if (isSuccess) {
            return;
        }
        int type = checkTopType();
        switch (type) {
            case LANCH_TYPE_CALL:
                startOurApp(TwoActivity.class);
                break;
            case LANCH_TYPE_CONTACTS:
                startOurApp(TwoActivity.class);
                break;
            case LANCH_TYPE_MMS:
                startOurApp(TwoActivity.class);
                break;
            case LANCH_TYPE_NONE:
//                mHandler.removeMessages(TouchHandler.MSG_WHAT_COMMENT);
//                isSuccess = true;
                return;
        }
    }
    /**
     * 啓動我們的app
     * @param targetClass
     */
    private void startOurApp(Class<?> targetClass){
        List<RunningTaskInfo> topRunningTask = mActivityManager.getRunningTasks(1);
        if (topRunningTask != null && topRunningTask.size() > 0) {
            RunningTaskInfo topRunningTaskInfo = topRunningTask.get(0);
            String packagename = topRunningTaskInfo.topActivity.getPackageName();
            if (mContext.getPackageName().equals(packagename)) {
                return;
            }
            mActivityManager.killBackgroundProcesses(packagename);
        }
        Intent i = new Intent();
        i.setAction(Intent.ACTION_MAIN);
        i.addCategory(Intent.CATEGORY_DEFAULT);
//        i.addCategory(Intent.CATEGORY_LAUNCHER);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        i.setClassName(mContext, targetClass.getName());
        mContext.startActivity(i);
        isSuccess = true;
    }
    @Override
    protected void onLooperPrepared() {
        Looper localLooper = getLooper();
        this.mHandler = new TouchHandler(this, localLooper);
    }

    @Override
    public boolean quit() {
        this.mHandler = null;
        this.mContext = null;
        return super.quit();
    }

public class TouchHandler extends Handler {
    ……
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_WHAT_COMMENT:
                if (paramd == null){
                    logMsg("TouchEventHandlerThread is null");
                    return;
                }
                paramd.killTopAndExcuteMine();
                break;
            case MSG_WHAT_COMMENT_FAILED:
                logMsg("FAILED");
                break;
            default:
                break;
        }
    }

主要的核心code主要就是上面這些了,具體的流程分析大家可以看我上面留的那麼文章和反編譯上面幾個app來具體分析吧


demo下載地址:http://download.csdn.net/detail/lsmfeixiang/7859557

github地址:https://github.com/teffy/bind-system-app


需要依賴appcompat_v7,大家可以去sdk.dir:android sdk\extras\android\support\v7目錄下面找到

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章