Back to project page ViewInPlay.
The source code is released under:
GNU General Public License
If you think the Android project ViewInPlay listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package info.daylemk.viewinplay; //from w w w . j a v a 2s .c o m import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.content.res.XModuleResources; import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.ScrollView; import android.widget.Toast; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XSharedPreferences; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; public class RecentTaskHook { private static final String TAG = "DayL"; private static final String TAG_CLASS = "[RecentTaskHook]"; static String TEXT_APP_INFO; // the string of the app info read from android static String TEXT_APP_INFO_STOCK = null; // re-open it static String TEXT_OPEN_IN_XHALO; static String TEXT_REMOVE_FROM_LIST; // the string of remove from list read from android static String TEXT_REMOVE_FROM_LIST_STOCK = null; static String TEXT_LAUNCH_FLOATING = null; // the string of launch floating read from android static String TEXT_LAUNCH_FLOATING_STOCK = null; static String TEXT_VIEW_IN_PLAY; static String TEXT_NO_PLAY; static String TEXT_STOCK_APP; // static String TEXT_CANT_OPEN_IN_XHALO; static int ID_REMOVE_FROM_LIST = 1000; static int ID_APP_INFO = 2000; static int ID_LAUNCH_FLOATING = 3000; // re-open it static final int ID_OPEN_IN_XHALO = 9999; static final int ID_VIEW_IN_PLAY = 9998; private static RecentsOnTouchLis sTouchListener; private static boolean bool_compat_xhalo = false; // 0 is a better choice private static int id_app_thumbnail = 0; private static int popupMenuId = 0; private static int recentRemoveItemId = 0; private static int inspectItemId = 0; // added PA floating mode private static int floatingItemId = 0; public static void initZygote(XModuleResources module_res) { TEXT_APP_INFO = module_res.getString(R.string.recents_app_info); // re-open it TEXT_OPEN_IN_XHALO = module_res.getString(R.string.recents_open_halo); TEXT_REMOVE_FROM_LIST = module_res.getString(R.string.recents_remove_from_list); TEXT_VIEW_IN_PLAY = module_res.getString(R.string.view_in_play); TEXT_NO_PLAY = module_res.getString(R.string.no_play_on_the_phone); TEXT_STOCK_APP = module_res.getString(R.string.stock_app); TEXT_LAUNCH_FLOATING = module_res.getString(R.string.recents_launch_floating); // TEXT_CANT_OPEN_IN_XHALO = // module_res.getString(R.string.cant_open_in_xhalo); } public static void handleLoadPackage(final LoadPackageParam lpp, final XSharedPreferences pref) { if (!lpp.packageName.equals("com.android.systemui")) return; Common.debugLog(TAG + TAG_CLASS + "handle package"); Common.debugLog(TAG + TAG_CLASS + "directlyShowInPlay = " + XposedInit.directlyShowInPlay); StringBuffer sb = new StringBuffer(); Set<String> set = pref.getAll().keySet(); Common.debugLog(TAG + TAG_CLASS + "size : " + set.size()); for (Iterator<String> iterator = set.iterator(); iterator.hasNext();) { sb.append(iterator.next() + ", "); } Common.debugLog(TAG + TAG_CLASS + "keys : " + sb.toString()); Common.debugLog(TAG + TAG_CLASS + "contain???" + pref.contains(XposedInit.KEY_SHOW_IN_RECENT_PANEL)); pref.reload(); if (pref.getBoolean(XposedInit.KEY_TWO_FINGER_IN_RECENT_PANEL, Common.DEFAULT_TWO_FINGER_IN_RECENT_PANEL)) { injectTouchEvents(lpp); } bool_compat_xhalo = pref.getBoolean(XposedInit.KEY_COMPAT_XHALO, Common.DEFAULT_COMPAT_XHALO); if (pref.getBoolean(XposedInit.KEY_SHOW_IN_RECENT_PANEL, Common.DEFAULT_SHOW_IN_RECENT_PANEL)) { injectMenu(lpp); } } private static void injectMenu(final LoadPackageParam lpp) { Common.debugLog(TAG + TAG_CLASS + "in inject menu"); final Class<?> hookClass = XposedHelpers.findClass( "com.android.systemui.recent.RecentsPanelView", lpp.classLoader); XposedBridge.hookAllMethods(hookClass, "handleLongPress", new XC_MethodHook() { protected void beforeHookedMethod(MethodHookParam param) throws Throwable { final View thiz = (View) param.thisObject; final View selectedView = (View) param.args[0]; final View anchorView = (View) param.args[1]; final View thumbnailView = (View) param.args[2]; thumbnailView.setSelected(true); final Object viewHolder = selectedView.getTag(); // if the app is stock app, we should not show the 'view in // play' menu String pkgName = getPackageName(viewHolder); Common.debugLog(TAG + TAG_CLASS + "the package is : " + pkgName); if (isAndroidStockApp(pkgName)) { // stock app, return Common.debugLog(TAG + TAG_CLASS + "stock app"); return; } Resources res = thiz.getContext().getResources(); if (recentRemoveItemId == 0) { recentRemoveItemId = res.getIdentifier("recent_remove_item", "id", "com.android.systemui"); } Common.debugLog(TAG + TAG_CLASS + "the recent remove menu id is " + recentRemoveItemId); if (recentRemoveItemId != 0) { ID_REMOVE_FROM_LIST = recentRemoveItemId; } if (inspectItemId == 0) { inspectItemId = res.getIdentifier("recent_inspect_item", "id", "com.android.systemui"); } XposedBridge .log(TAG + TAG_CLASS + "the recent inspect menu id is " + inspectItemId); if (inspectItemId != 0) { ID_APP_INFO = inspectItemId; } // PA floating // MOD, always get the it if (floatingItemId == 0) { floatingItemId = res.getIdentifier("recent_launch_floating", "id", "com.android.systemui"); } XposedBridge .log(TAG + TAG_CLASS + "the recent floating menu id is " + floatingItemId); if (floatingItemId != 0) { ID_LAUNCH_FLOATING = floatingItemId; } PopupMenu mPopupMenu = new PopupMenu(thiz.getContext(), anchorView == null ? selectedView : anchorView); if (popupMenuId == 0) { popupMenuId = res.getIdentifier("recent_popup_menu", "menu", "com.android.systemui"); } // if we didn't get the menu xml, we should build it if (popupMenuId == 0) { if (TEXT_REMOVE_FROM_LIST_STOCK == null || TEXT_APP_INFO_STOCK == null) { try { TEXT_REMOVE_FROM_LIST_STOCK = res.getString(res.getIdentifier( "status_bar_recent_remove_item_title", "string", "com.android.systemui")); TEXT_APP_INFO_STOCK = res.getString(res.getIdentifier( "status_bar_recent_inspect_item_title", "string", "com.android.systemui")); // added PA floating mode if (XposedInit.bool_compat_floating) { TEXT_LAUNCH_FLOATING_STOCK = res.getString(res.getIdentifier( "status_bar_recent_floating_item_title", "string", "com.android.systemui")); } } catch (Exception e) { Common.debugLog(TAG + TAG_CLASS + "can't get the text of stock text"); } // if we have exception here // if the text is null // if the text is empty // in these conditions, we all needs set the text to the // default // if we set some text to the default boolean isSet = false; if (TEXT_REMOVE_FROM_LIST_STOCK == null || TEXT_REMOVE_FROM_LIST_STOCK.equals("")) { TEXT_REMOVE_FROM_LIST_STOCK = TEXT_REMOVE_FROM_LIST; Common.debugLog(TAG + TAG_CLASS + "set the text to the default "); isSet = true; } if (TEXT_APP_INFO_STOCK == null || TEXT_APP_INFO_STOCK.equals("")) { TEXT_APP_INFO_STOCK = TEXT_APP_INFO; Common.debugLog(TAG + TAG_CLASS + "set the text to the default "); isSet = true; } // PA floating // MOD, always if (TEXT_LAUNCH_FLOATING_STOCK == null || TEXT_LAUNCH_FLOATING_STOCK.equals("")) { TEXT_LAUNCH_FLOATING_STOCK = TEXT_LAUNCH_FLOATING; Common.debugLog(TAG + TAG_CLASS + "set the text to the default "); isSet = true; } if (!isSet) { Common.debugLog(TAG + TAG_CLASS + "got the text"); } } mPopupMenu.getMenu().add(Menu.NONE, ID_REMOVE_FROM_LIST, 1, TEXT_REMOVE_FROM_LIST_STOCK); mPopupMenu.getMenu().add(Menu.NONE, ID_APP_INFO, 2, TEXT_APP_INFO_STOCK); // the PA floating compatibility is only available on the PA // ROM if (XposedInit.bool_compat_floating) { Common.debugLog(TAG + TAG_CLASS + "the PA floating compatibility"); mPopupMenu.getMenu().add(Menu.NONE, ID_LAUNCH_FLOATING, 5, TEXT_LAUNCH_FLOATING); } } else { mPopupMenu.getMenuInflater().inflate( popupMenuId, mPopupMenu.getMenu()); } // if the xhalo compatibility is on, add that menu if (bool_compat_xhalo) { Common.debugLog(TAG + TAG_CLASS + "the xhalo compatibility"); // re-open it mPopupMenu.getMenu().add(Menu.NONE, ID_OPEN_IN_XHALO, 3, TEXT_OPEN_IN_XHALO); } Common.debugLog(TAG + TAG_CLASS + "show the vip menu : " + pkgName); mPopupMenu.getMenu().add(Menu.NONE, ID_VIEW_IN_PLAY, 4, TEXT_VIEW_IN_PLAY); try { XposedHelpers.setObjectField(thiz, "mPopup", mPopupMenu); // thiz.getClass().getDeclared!1Field("mPopup").set(thiz, // popup); } catch (Exception e) { // User on ICS } final PopupMenu.OnMenuItemClickListener menu = new PopupMenu.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { int itemId = item.getItemId(); Common.debugLog(TAG + TAG_CLASS + "item clicked : " + itemId); try { if (itemId == ID_REMOVE_FROM_LIST) { Field filedRC = hookClass.getDeclaredField("mRecentsContainer"); filedRC.setAccessible(true); ViewGroup recentsContainer = (ViewGroup) filedRC.get(thiz); recentsContainer.removeViewInLayout(selectedView); return true; } else if (itemId == ID_APP_INFO) { if (viewHolder != null) { closeRecentApps(thiz); String pkgName = getPackageName(viewHolder); try { Method method = thiz.getClass().getDeclaredMethod( "startApplicationDetailsActivity", String.class); method.setAccessible(true); method.invoke(thiz, pkgName); } catch (Exception e) { XposedBridge.log(e); // if we can't start it, use my way startApplicationDetailsActivity(thiz.getContext(), pkgName); } } return true; } else if (itemId == ID_OPEN_IN_XHALO) { if (viewHolder != null) { closeRecentApps(thiz); Intent intent = getIntentFromViewHolder(viewHolder); intent.addFlags(Common.FLAG_FLOATING_WINDOW | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK); thiz.getContext().startActivity(intent); } return true; } else if (itemId == ID_VIEW_IN_PLAY) { if (viewHolder != null) { closeRecentApps(thiz); viewInPlay(thiz.getContext(), getPackageName(viewHolder)); } return true; } else if (itemId == ID_LAUNCH_FLOATING) { if (viewHolder != null) { closeRecentApps(thiz); Intent intent = getIntentFromViewHolder(viewHolder); intent.setFlags(Common.FLAG_FLOATING_WINDOW | Intent.FLAG_ACTIVITY_NEW_TASK); thiz.getContext().startActivity(intent); } } } catch (Throwable t) { Common.debugLog(Common.LOG_TAG); XposedBridge.log(t); } return false; } }; mPopupMenu.setOnMenuItemClickListener(menu); mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() { public void onDismiss(PopupMenu menu) { thumbnailView.setSelected(false); try { XposedHelpers.setObjectField(thiz, "mPopup", null); // thiz.getClass().getDeclared!1Field("mPopup").set(thiz, // null); } catch (Exception e) { // User on ICS } } }); mPopupMenu.show(); param.setResult(null); } }); } private static void injectTouchEvents(final LoadPackageParam lpp) { Common.debugLog(TAG + TAG_CLASS + "in inject touch eventss"); final Class<?> hookClass = XposedHelpers.findClass( "com.android.systemui.recent.RecentsVerticalScrollView", lpp.classLoader); XposedBridge.hookAllMethods(hookClass, "update", new XC_MethodHook() { protected void afterHookedMethod(MethodHookParam param) throws Throwable { final Object thiz = param.thisObject; final ScrollView sv = (ScrollView) thiz; Resources res = sv.getContext().getResources(); if (id_app_thumbnail == 0) { id_app_thumbnail = res.getIdentifier("app_thumbnail", "id", "com.android.systemui"); Common.debugLog(TAG + TAG_CLASS + "identifier 2: " + id_app_thumbnail); } LinearLayout mLinearLayout = null; try { Field fieldLL = thiz.getClass().getDeclaredField("mLinearLayout"); fieldLL.setAccessible(true); mLinearLayout = (LinearLayout) fieldLL.get(thiz); } catch (Exception e) { XposedBridge .log(TAG + TAG_CLASS + "Exception in linear layout, can't find mLinearLayout"); } if (mLinearLayout != null) { Common.debugLog(TAG + TAG_CLASS + "in inject touch events, OK"); if (sTouchListener == null) { Common.debugLog(TAG + TAG_CLASS + "new a touch listener"); sTouchListener = new RecentsOnTouchLis(); } FrameLayout frameLayout; for (int i = 0; i < mLinearLayout.getChildCount(); i++) { Common.debugLog(TAG + TAG_CLASS + "order = " + i); frameLayout = (FrameLayout) mLinearLayout.getChildAt(i); // frameLayout.setOnTouchListener(sTouchListener); // set the touch listener to at the thumb nail, so we // can get finger count frameLayout.findViewById(id_app_thumbnail).setOnTouchListener( sTouchListener); } } else { Common.debugLog(TAG + TAG_CLASS + "in inject touch events, can't get linearLayout"); } } }); // different class injection final Class<?> panelViewHookClass = XposedHelpers.findClass( "com.android.systemui.recent.RecentsPanelView", lpp.classLoader); XposedBridge.hookAllMethods(panelViewHookClass, "handleOnClick", new XC_MethodHook() { protected void beforeHookedMethod(MethodHookParam param) throws Throwable { // the touch listener is null??? any way, return if (sTouchListener == null) { Common.debugLog(TAG + TAG_CLASS + "EEROR, the touch listener is null"); return; } // not the two finger click action, return if (!sTouchListener.twoFingerClick) { Common.debugLog(TAG + TAG_CLASS + "not two finger tap"); return; } sTouchListener.twoFingerClick = false; Common.debugLog(TAG + TAG_CLASS + "playing in the two finger tap onClick"); final View thiz = (View) param.thisObject; final View selectedView = (View) param.args[0]; final Object viewHolder = selectedView.getTag(); // if the app is stock app, we should not go to plays String pkgName = getPackageName(viewHolder); Common.debugLog(TAG + TAG_CLASS + "the package is : " + pkgName); if (isAndroidStockApp(pkgName)) { // stock app, show toast and do nothing Common.debugLog(TAG + TAG_CLASS + "stock app"); Toast.makeText(thiz.getContext(), TEXT_STOCK_APP, Toast.LENGTH_SHORT).show(); param.setResult(null); return; } closeRecentApps(thiz); // Object ad = viewHolder.getClass() // .getDeclared!1Field("taskDescription") // .get(viewHolder); // String pkg_name = (String) // ad.getClass() // .getDeclared!1Field("packageName").get(ad); viewInPlay(thiz.getContext(), getPackageName(viewHolder)); param.setResult(null); } }); } private static class RecentsOnTouchLis implements View.OnTouchListener { public boolean twoFingerClick; @Override public boolean onTouch(View v, MotionEvent event) { Common.debugLog(TAG + TAG_CLASS + "on touch, v : " + v + ", events : " + event); Common.debugLog(TAG + TAG_CLASS + "events, action : " + event.getAction()); Common.debugLog(TAG + TAG_CLASS + "events, pointer count : " + event.getPointerCount()); if (event.getPointerCount() == 2) { Common.debugLog(TAG + TAG_CLASS + "events, two pointer"); if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { twoFingerClick = true; Common.debugLog(TAG + TAG_CLASS + "events, pointer DOWN"); XposedBridge .log(TAG + TAG_CLASS + "events, TWO ON, e: " + event); } } else { // don't set it // twoFingerClick = false; } return false; } } private static void closeRecentApps(View thiz) { // > 4.1 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) { Common.debugLog(TAG + TAG_CLASS + "call show, >4.1"); try { // DO NOT use callMethod, it wouldn't catch anything here Method methodShow = thiz.getClass().getDeclaredMethod("show", boolean.class); methodShow.setAccessible(true); methodShow.invoke(thiz, false); // XposedHelpers.callMethod(thiz, "dismissAndGoBack"); return; } catch (Exception e) { XposedBridge.log(e); } } // 4.1 StatusBarHook.collapseStatusBarPanel(); /* * new Thread() { * @Override public void run() { try { * Runtime.getRuntime().exec("input keyevent " + KeyEvent.KEYCODE_BACK); * } catch (Exception e) { Common.debugLog(e); } } }.start(); */ } // we use this method in the Status bar hook /* private */static void startApplicationDetailsActivity(Context ctx, String packageName) { Common.debugLog(TAG + TAG_CLASS + "start application details use my way"); Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts( "package", packageName, null)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctx.startActivity(intent); } /** * get the package name from the view holder object * * @param viewHolder * @return * @throws Throwable */ private static String getPackageName(final Object viewHolder) throws Throwable { String pkg_name = ""; if (viewHolder != null) { Field fieldTD = viewHolder.getClass().getDeclaredField("taskDescription"); fieldTD.setAccessible(true); Object ad = fieldTD.get(viewHolder); Field fieldPN = ad.getClass() .getDeclaredField("packageName"); fieldPN.setAccessible(true); pkg_name = (String) fieldPN.get(ad); } return pkg_name; } private static Intent getIntentFromViewHolder(Object viewHolder) { Object ad; try { Field fieldTD = viewHolder.getClass().getDeclaredField("taskDescription"); fieldTD.setAccessible(true); ad = fieldTD.get(viewHolder); Field fieldIntent = ad.getClass().getDeclaredField("intent"); fieldIntent.setAccessible(true); return (Intent) fieldIntent.get(ad); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } Common.debugLog(TAG + TAG_CLASS + "get intent error"); return new Intent(); } /** * check if the package is the stock app or not added : Null check * * @param pkgName * @return */ static boolean isAndroidStockApp(String pkgName) { if (pkgName == null) { Common.debugLog(TAG + TAG_CLASS + "the package name is null, isAndroidStockApp"); return false; } // to lower case String pkgNameInner = pkgName.toLowerCase(Locale.ENGLISH); if (XposedInit.isStockAndroidApp(pkgNameInner)) return true; if (XposedInit.isNotStockApp(pkgNameInner)) return false; if (pkgNameInner.startsWith("com.android")) return true; // this is for the notification package name if (pkgNameInner.equals("android")) return true; return false; } /** * view the package in play, use the default access permission, so we can * call this method in this package * * @param ctx * @param packageName */ static void viewInPlay(Context ctx, String packageName) { Common.debugLog(TAG + TAG_CLASS + "view in play : " + packageName); Intent intent = new Intent(Intent.ACTION_VIEW); Common.debugLog(TAG + TAG_CLASS + "use browser : " + XposedInit.useBrowser); // add browser-store switch if (XposedInit.useBrowser) { intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageName)); } else { // move to this, so can view in different app store intent.setData(Uri.parse("market://details?id=" + packageName)); } intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME); checkPlay(ctx, intent); ctx.startActivity(intent); } private static void checkPlay(Context ctx, Intent intent) { List<ResolveInfo> list = ctx.getPackageManager().queryIntentActivities(intent, 0); Common.debugLog(TAG + TAG_CLASS + "directlyShowInPlay in check play : " + XposedInit.directlyShowInPlay); if (XposedInit.directlyShowInPlay) { String pkgName; Common.debugLog(TAG + TAG_CLASS + "size:" + list.size()); for (int i = 0; i < list.size(); i++) { ActivityInfo info = list.get(i).activityInfo; Common.debugLog(TAG + TAG_CLASS + "the package name is " + info.packageName); pkgName = info.packageName; if (pkgName.equals("com.android.vending")) { Common.debugLog(TAG + TAG_CLASS + "we found it : " + pkgName); String appInfo = info.toString(); Common.debugLog(TAG + TAG_CLASS + "app info string : " + appInfo); String activityName = appInfo.substring( appInfo.lastIndexOf(" ") + 1, appInfo.length() - 1); Common.debugLog(TAG + TAG_CLASS + "activity name : " + activityName); intent.setComponent(new ComponentName(pkgName, activityName)); return; } } Common.debugLog(TAG + TAG_CLASS + "no play here, pity"); // if didn't find the Google Play Store, show the toast // TODO Toast may can not show by system, so we should do later // about it, maybe a broadcast instead // DONE just need to get the resource at init moment Toast.makeText(ctx, TEXT_NO_PLAY, Toast.LENGTH_LONG).show(); } } }