您的位置:首页 > 移动开发 > Android开发

Android绑定系统图标,类似各种电话本的绑定图标功能

2014-12-28 13:13 621 查看
这是一篇我个人在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目录下面找到
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息