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目录下面找到

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值