/*
** DroidPlugin Project
**
** Copyright(c) 2015 Andy Zhang <zhangyong232@gmail.com>
**
** This file is part of DroidPlugin.
**
** DroidPlugin is free software: you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation, either
** version 3 of the License, or (at your option) any later version.
**
** DroidPlugin is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with DroidPlugin. If not, see <http://www.gnu.org/licenses/lgpl.txt>
**
**/
package com.morgoo.droidplugin.hook.handle;
import android.app.Application;
import android.app.Notification;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.RemoteViews;
import com.morgoo.droidplugin.core.PluginProcessManager;
import com.morgoo.droidplugin.hook.BaseHookHandle;
import com.morgoo.droidplugin.hook.HookedMethodHandler;
import com.morgoo.droidplugin.pm.PluginManager;
import com.morgoo.droidplugin.reflect.FieldUtils;
import com.morgoo.helper.Log;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Created by Andy Zhang(zhangyong232@gmail.com) on 2015/2/28.
*/
public class INotificationManagerHookHandle extends BaseHookHandle {
private static final String TAG = INotificationManagerHookHandle.class.getSimpleName();
public INotificationManagerHookHandle(Context context) {
super(context);
}
@Override
protected void init() {
init1();
sHookedMethodHandlers.put("enqueueNotification", new enqueueNotification(mHostContext));
sHookedMethodHandlers.put("cancelNotification", new cancelNotification(mHostContext));
sHookedMethodHandlers.put("cancelAllNotifications", new cancelAllNotifications(mHostContext));
sHookedMethodHandlers.put("enqueueToast", new enqueueToast(mHostContext));
sHookedMethodHandlers.put("cancelToast", new cancelToast(mHostContext));
sHookedMethodHandlers.put("enqueueNotificationWithTag", new enqueueNotificationWithTag(mHostContext));
sHookedMethodHandlers.put("enqueueNotificationWithTagPriority", new enqueueNotificationWithTagPriority(mHostContext));
sHookedMethodHandlers.put("cancelNotificationWithTag", new cancelNotificationWithTag(mHostContext));
sHookedMethodHandlers.put("setNotificationsEnabledForPackage", new setNotificationsEnabledForPackage(mHostContext));
sHookedMethodHandlers.put("areNotificationsEnabledForPackage", new areNotificationsEnabledForPackage(mHostContext));
}
// public void cancelAllNotifications(String pkg, int userId);
// public void enqueueToast(String pkg, ITransientNotification callback, int duration);
// public void cancelToast(String pkg, ITransientNotification callback);
// public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, Notification notification, int[] idReceived, int userId);
// public void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
// public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
// public boolean areNotificationsEnabledForPackage(String pkg, int uid);
// public void setPackagePriority(String pkg, int uid, int priority);
// public int getPackagePriority(String pkg, int uid);
// public void setPackageVisibilityOverride(String pkg, int uid, int visibility);
// public int getPackageVisibilityOverride(String pkg, int uid);
// public StatusBarNotification[] getActiveNotifications(String callingPkg);
// public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
// public void registerListener(INotificationListener listener, ComponentName component, int userid);
// public void unregisterListener(INotificationListener listener, int userid);
// public void cancelNotificationFromListener(INotificationListener token, String pkg, String tag, int id);
// public void cancelNotificationsFromListener(INotificationListener token, String[] keys);
// public pm.ParceledListSlice getActiveNotificationsFromListener(INotificationListener token, String[] keys, int trim);
// public void requestHintsFromListener(INotificationListener token, int hints);
// public int getHintsFromListener(INotificationListener token);
// public void requestInterruptionFilterFromListener(INotificationListener token, int interruptionFilter);
// public int getInterruptionFilterFromListener(INotificationListener token);
// public void setOnNotificationPostedTrimFromListener(INotificationListener token, int trim);
// public ComponentName getEffectsSuppressor();
// public boolean matchesCallFilter(android.os.Bundle extras);
// public ZenModeConfig getZenModeConfig();
// public boolean setZenModeConfig(ZenModeConfig config);
// public void notifyConditions(String pkg, IConditionProvider provider, Condition[] conditions);
// public void requestZenModeConditions(IConditionListener callback, int relevance);
// public void setZenModeCondition(Condition condition);
// public void setAutomaticZenModeConditions(android.net.Uri[] conditionIds);
// public Condition[] getAutomaticZenModeConditions();
private static Map<Integer, String> sSystemLayoutResIds = new HashMap<Integer, String>(0);
private static void init1() {
try {
//read all com.android.internal.R
Class clazz = Class.forName("com.android.internal.R$layout");
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//public static final
if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {
try {
int id = field.getInt(null);
sSystemLayoutResIds.put(id, field.getName());
} catch (IllegalAccessException e) {
Log.e(TAG, "read com.android.internal.R$layout.%s", e, field.getName());
}
}
}
} catch (Exception e) {
Log.e(TAG, "read com.android.internal.R$layout", e);
}
}
public static int getResIdByName(String name) {
for (Integer integer : sSystemLayoutResIds.keySet()) {
if (TextUtils.equals(name, sSystemLayoutResIds.get(integer))) {
return integer;
}
}
return -1;
}
private class MyNotification extends HookedMethodHandler {
public MyNotification(Context context) {
super(context);
}
@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
int index = 0;
if (args != null && args.length > index) {
if (args[index] instanceof String) {
String pkg = (String) args[index];
if (!TextUtils.equals(pkg, mHostContext.getPackageName())) {
args[index] = mHostContext.getPackageName();
}
}
}
return super.beforeInvoke(receiver, method, args);
}
}
private int findFisrtNotificationIndex(Object[] args) {
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Notification) {
return i;
}
}
}
return -1;
}
private class enqueueNotification extends MyNotification {
public enqueueNotification(Context context) {
super(context);
}
//2.3.2_r1, 4.0.1_r1
/* public void enqueueNotification(String pkg, int id, Notification notification, int[] idReceived);*/
@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
final int index = 0;
if (args != null && args.length > index && args[index] instanceof String) {
String pkg = (String) args[index];
if (!TextUtils.equals(pkg, mHostContext.getPackageName())) {
args[index] = mHostContext.getPackageName();
}
}
final int index2 = findFisrtNotificationIndex(args);
if (index2 >= 0) {
Notification notification = (Notification) args[index2];//nobug
if (isPluginNotification(notification)) {
if (shouldBlock(notification)) {
Log.e(TAG, "We has blocked a notification[%s]", notification);
return true;
} else {
//这里要修改通知。
hackNotification(notification);
return false;
}
}
}
return false;
}
}
private void hackRemoteViews(RemoteViews remoteViews) throws IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
if (remoteViews != null && !TextUtils.equals(remoteViews.getPackage(), mHostContext.getPackageName())) {
if (sSystemLayoutResIds.containsKey(remoteViews.getLayoutId())) {
Object mActionsObj = FieldUtils.readField(remoteViews, "mActions");
if (mActionsObj instanceof Collection) {
Collection mActions = (Collection) mActionsObj;
String aPackage = remoteViews.getPackage();
Application pluginContent = PluginProcessManager.getPluginContext(aPackage);
if (pluginContent != null) {
Iterator iterable = mActions.iterator();
Class TextViewDrawableActionClass = null;
try {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
TextViewDrawableActionClass = Class.forName(RemoteViews.class.getName() + "$TextViewDrawableAction");
}
} catch (ClassNotFoundException e) {
}
Class ReflectionActionClass = Class.forName(RemoteViews.class.getName() + "$ReflectionAction");
while (iterable.hasNext()) {
Object action = iterable.next();
if (ReflectionActionClass.isInstance(action)) {//???这里这样是对的么?
String methodName = (String) FieldUtils.readField(action, "methodName");
//String methodName;,int type; Object value;
if ("setImageResource".equals(methodName)) { //setInt(viewId, "setImageResource", srcId);
Object BITMAP = FieldUtils.readStaticField(action.getClass(), "BITMAP");
int resId = (Integer) FieldUtils.readField(action, "value");
Bitmap bitmap = BitmapFactory.decodeResource(pluginContent.getResources(), resId);
FieldUtils.writeField(action, "type", BITMAP);
FieldUtils.writeField(action, "value", bitmap);
FieldUtils.writeField(action, "methodName", "setImageBitmap");
} else if ("setImageURI".equals(methodName)) {//setUri(viewId, "setImageURI", uri);
iterable.remove(); //TODO RemoteViews.setImageURI 其实应该适配的。
} else if ("setLabelFor".equals(methodName)) {
iterable.remove(); //TODO RemoteViews.setLabelFor 其实应该适配的。
}
} else if (TextViewDrawableActionClass != null && TextViewDrawableActionClass.isInstance(action)) {
iterable.remove();
// if ("setTextViewCompoundDrawables".equals(methodName)) {
// iterable.remove(); //TODO RemoteViews.setTextViewCompoundDrawables 其实应该适配的。
// } else if ("setTextViewCompoundDrawablesRelative".equals(methodName)) {
// iterable.remove(); //TODO RemoteViews.setTextViewCompoundDrawablesRelative 其实应该适配的。
// }
}
}
}
}
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
FieldUtils.writeField(remoteViews, "mApplication", mHostContext.getApplicationInfo());
} else {
FieldUtils.writeField(remoteViews, "mPackage", mHostContext.getPackageName());
}
}
}
private boolean shouldBlockByRemoteViews(RemoteViews remoteViews) {
if (remoteViews == null) {
return false;
} else if (remoteViews != null && sSystemLayoutResIds.containsKey(remoteViews.getLayoutId())) {
return false;
} else {
return true;
}
}
private boolean shouldBlock(Notification notification) {
if (shouldBlockByRemoteViews(notification.contentView)) {
return true;
}
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
if (shouldBlockByRemoteViews(notification.tickerView)) {
return true;
}
}
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
if (shouldBlockByRemoteViews(notification.bigContentView)) {
return true;
}
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
if (shouldBlockByRemoteViews(notification.headsUpContentView)) {
return true;
}
}
return false;
}
private boolean isPluginNotification(Notification notification) {
if (notification == null) {
return false;
}
if (notification.contentView != null && !TextUtils.equals(mHostContext.getPackageName(), notification.contentView.getPackage())) {
return true;
}
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
if (notification.tickerView != null && !TextUtils.equals(mHostContext.getPackageName(), notification.tickerView.getPackage())) {
return true;
}
}
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
if (notification.bigContentView != null && !TextUtils.equals(mHostContext.getPackageName(), notification.bigContentView.getPackage())) {
return true;
}
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
if (notification.headsUpContentView != null && !TextUtils.equals(mHostContext.getPackageName(), notification.headsUpContentView.getPackage())) {
return true;
}
}
if (VERSION.SDK_INT >= VERSION_CODES.M) {
android.graphics.drawable.Icon icon = notification.getSmallIcon();
if (icon != null) {
try {
Object mString1Obj = FieldUtils.readField(icon, "mString1", true);
if (mString1Obj instanceof String) {
String mString1 = ((String) mString1Obj);
if (PluginManager.getInstance().isPluginPackage(mString1)) {
return true;
}
}
} catch (Exception e) {
Log.e(TAG, "fix Icon.smallIcon", e);
}
}
}
if (VERSION.SDK_INT >= VERSION_CODES.M) {
android.graphics.drawable.Icon icon = notification.getLargeIcon();
if (icon != null) {
try {
Object mString1Obj = FieldUtils.readField(icon, "mString1", true);
if (mString1Obj instanceof String) {
String mString1 = ((String) mString1Obj);
if (PluginManager.getInstance().isPluginPackage(mString1)) {
return true;
}
}
} catch (Exception e) {
Log.e(TAG, "fix Icon.smallIcon", e);
}
}
}
try {
Bundle mExtras = (Bundle) FieldUtils.readField(notification, "extras", true);
for (String s : mExtras.keySet()) {
if (mExtras.get(s) != null && mExtras.get(s) instanceof ApplicationInfo) {
ApplicationInfo applicationInfo = (ApplicationInfo) mExtras.get(s);
return !TextUtils.equals(mHostContext.getPackageName(), applicationInfo.packageName);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
private Bitmap drawableToBitMap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = ((BitmapDrawable) drawable);
return bitmapDrawable.getBitmap();
} else {
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
}
private void hackNotification(Notification notification) throws IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
// remoteViews com.android.internal.R.layout.notification_template_material_media
// com.android.internal.R.layout.notification_template_material_big_media_narrow;
// com.android.internal.R.layout.notification_template_material_big_media;
// //getBaseLayoutResource
// R.layout.notification_template_material_base;
// //getBigBaseLayoutResource
// R.layout.notification_template_material_big_base;
// //getBigPictureLayoutResource
// R.layout.notification_template_material_big_picture;
// //getBigTextLayoutResource
// R.layout.notification_template_material_big_text;
// //getInboxLayoutResource
// R.layout.notification_template_material_inbox;
// //getActionLayoutResource
// R.layout.notification_material_action;
// //getActionTombstoneLayoutResource
// R.layout.notification_material_action_tombstone;
if (notification != null) {
notification.icon = mHostContext.getApplicationInfo().icon;
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
hackRemoteViews(notification.tickerView);
}
hackRemoteViews(notification.contentView);
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
hackRemoteViews(notification.bigContentView);
}
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
hackRemoteViews(notification.headsUpContentView);
}
if (VERSION.SDK_INT >= VERSION_CODES.M) {
android.graphics.drawable.Icon icon = notification.getSmallIcon();
if (icon != null) {
Bitmap bitmap = drawableToBitMap(icon.loadDrawable(mHostContext));
if (bitmap != null) {
android.graphics.drawable.Icon newIcon = android.graphics.drawable.Icon.createWithBitmap(bitmap);
FieldUtils.writeField(notification, "mSmallIcon", newIcon, true);
}
}
}
if (VERSION.SDK_INT >= VERSION_CODES.M) {
android.graphics.drawable.Icon icon = notification.getLargeIcon();
if (icon != null) {
Bitmap bitmap = drawableToBitMap(icon.loadDrawable(mHostContext));
if (bitmap != null) {
android.graphics.drawable.Icon newIcon = android.graphics.drawable.Icon.createWithBitmap(bitmap);
FieldUtils.writeField(notification, "mLargeIcon", newIcon, true);
}
}
}
}
}
private class cancelNotification extends MyNotification {
public cancelNotification(Context context) {
super(context);
}
//2.3.2_r1, 4.0.1_r1
/* public void cancelNotification(String pkg, int id);*/
}
private class cancelAllNotifications extends MyNotification {
public cancelAllNotifications(Context context) {
super(context);
}
//2.3.2_r1, 4.0.1_r1
/* public void cancelAllNotifications(String pkg);*/
}
private class enqueueToast extends MyNotification {
public enqueueToast(Context context) {
super(context);
}
//2.3.2_r1, 4.0.1_r1
/* public void enqueueToast(String pkg, ITransientNotification callback, int duration) ;*/
@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
//这里适配在android 5.0的机器上无法现实toast的问题。但是我也不知道还有那些机器需要这样做。
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
int index = 1;
if (args != null && args.length > index) {
Object obj = args[index];
View view = (View) FieldUtils.readField(obj, "mView");
View nextView = (View) FieldUtils.readField(obj, "mNextView");
if (nextView != null) {
FieldUtils.writeField(nextView, "mContext", mHostContext);
}
if (view != null) {
FieldUtils.writeField(view, "mContext", mHostContext);
}
}
}
return super.beforeInvoke(receiver, method, args);
}
}
private class cancelToast extends MyNotification {
public cancelToast(Context context) {
super(context);
}
//2.3.2_r1, 4.0.1_r1
/* public void cancelToast(String pkg, ITransientNotification callback);*/
@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
int index = 1;
if (args != null && args.length > index) {
Object obj = args[index];
View view = (View) FieldUtils.readField(obj, "mView");
View nextView = (View) FieldUtils.readField(obj, "mNextView");
if (nextView != null) {
FieldUtils.writeField(nextView, "mContext", mHostContext);
}
if (view != null) {
FieldUtils.writeField(view, "mContext", mHostContext);
}
}
return super.beforeInvoke(receiver, method, args);
}
}
private class enqueueNotificationWithTag extends MyNotification {
public enqueueNotificationWithTag(Context context) {
super(context);
}
//2.3.2_r1, 4.0.1_r1
/*public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification, int[] idReceived) ;*/
@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
final int index = 0;
if (args != null && args.length > index && args[index] instanceof String) {
String pkg = (String) args[index];
if (!TextUtils.equals(pkg, mHostContext.getPackageName())) {
args[index] = mHostContext.getPackageName();
}
}
final int index2 = findFisrtNotificationIndex(args);
if (index2 >= 0) {
Notification notification = (Notification) args[index2];//nobug
if (isPluginNotification(notification)) {
if (shouldBlock(notification)) {
Log.e(TAG, "We has blocked a notification[%s]", notification);
return true;
} else {
//这里要修改通知。
hackNotification(notification);
return false;
}
}
}
return false;
}
}
private class enqueueNotificationWithTagPriority extends MyNotification {
public enqueueNotificationWithTagPriority(Context context) {
super(context);
}
//4.0.1_r1
/*public void enqueueNotificationWithTagPriority(String pkg, String tag, int id, int priority, Notification notification, int[] idReceived);*/
@Override
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
final int index = 0;
if (args != null && args.length > index && args[index] instanceof String) {
String pkg = (String) args[index];
if (!TextUtils.equals(pkg, mHostContext.getPackageName())) {
args[index] = mHostContext.getPackageName();
}
}
final int index2 = findFisrtNotificationIndex(args);
if (index2 >= 0) {
Notification notification = (Notification) args[index2];//nobug
if (isPluginNotification(notification)) {
if (shouldBlock(notification)) {
Log.e(TAG, "We has blocked a notification[%s]", notification);
return true;
} else {
//这里要修改通知。
hackNotification(notification);
return false;
}
}
}
return false;
}
}
private class cancelNotificationWithTag extends MyNotification {
public cancelNotificationWithTag(Context context) {
super(context);
}
//2.3.2_r1, 4.0.1_r1
/* public void cancelNotificationWithTag(String pkg, String tag, int id) ;*/
}
private class setNotificationsEnabledForPackage extends MyNotification {
public setNotificationsEnabledForPackage(Context context) {
super(context);
}
}
private class areNotificationsEnabledForPackage extends MyNotification {
public areNotificationsEnabledForPackage(Context context) {
super(context);
}
}
}