package de.robv.android.xposed.mods.appsettings.hooks;
import static de.robv.android.xposed.XposedHelpers.callMethod;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import static de.robv.android.xposed.XposedHelpers.findClass;
import static de.robv.android.xposed.XposedHelpers.getObjectField;
import static de.robv.android.xposed.XposedHelpers.setObjectField;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.util.Log;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.mods.appsettings.Common;
import de.robv.android.xposed.mods.appsettings.XposedMod;
public class PackagePermissions extends BroadcastReceiver {
private final Object pmSvc;
private final Map<String, Object> mPackages;
private final Object mSettings;
@SuppressWarnings("unchecked")
public PackagePermissions(Object pmSvc) {
this.pmSvc = pmSvc;
this.mPackages = (Map<String, Object>) getObjectField(pmSvc, "mPackages");
this.mSettings = getObjectField(pmSvc, "mSettings");
}
public static void initHooks() {
/* Hook to the PackageManager service in order to
* - Listen for broadcasts to apply new settings and restart the app
* - Intercept the permission granting function to remove disabled permissions
*/
try {
final Class<?> clsPMS = findClass("com.android.server.pm.PackageManagerService", XposedMod.class.getClassLoader());
// Listen for broadcasts from the Settings part of the mod, so it's applied immediately
findAndHookMethod(clsPMS, "systemReady", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param)
throws Throwable {
Context mContext = (Context) getObjectField(param.thisObject, "mContext");
mContext.registerReceiver(new PackagePermissions(param.thisObject),
new IntentFilter(Common.MY_PACKAGE_NAME + ".UPDATE_PERMISSIONS"),
Common.MY_PACKAGE_NAME + ".BROADCAST_PERMISSION",
null);
}
});
// if the user has disabled certain permissions for an app, do as if the hadn't requested them
findAndHookMethod(clsPMS, "grantPermissionsLPw", "android.content.pm.PackageParser$Package", boolean.class,
new XC_MethodHook() {
@SuppressWarnings("unchecked")
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
String pkgName = (String) getObjectField(param.args[0], "packageName");
if (!XposedMod.isActive(pkgName) || !XposedMod.prefs.getBoolean(pkgName + Common.PREF_REVOKEPERMS, false))
return;
Set<String> disabledPermissions = XposedMod.prefs.getStringSet(pkgName + Common.PREF_REVOKELIST, null);
if (disabledPermissions == null || disabledPermissions.isEmpty())
return;
ArrayList<String> origRequestedPermissions = (ArrayList<String>) getObjectField(param.args[0], "requestedPermissions");
param.setObjectExtra("orig_requested_permissions", origRequestedPermissions);
ArrayList<String> newRequestedPermissions = new ArrayList<String>(origRequestedPermissions.size());
for (String perm: origRequestedPermissions) {
if (!disabledPermissions.contains(perm))
newRequestedPermissions.add(perm);
else
// you requested those internet permissions? I didn't read that, sorry
Log.w(Common.TAG, "Not granting permission " + perm
+ " to package " + pkgName
+ " because you think it should not have it");
}
setObjectField(param.args[0], "requestedPermissions", newRequestedPermissions);
}
@SuppressWarnings("unchecked")
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// restore requested permissions if they were modified
ArrayList<String> origRequestedPermissions = (ArrayList<String>) param.getObjectExtra("orig_requested_permissions");
if (origRequestedPermissions != null)
setObjectField(param.args[0], "requestedPermissions", origRequestedPermissions);
}
});
} catch (Throwable e) {
XposedBridge.log(e);
}
}
@Override
public void onReceive(Context context, Intent intent) {
try {
// The app broadcasted a request to update settings for a running app
// Validate the action being requested
if (!Common.ACTION_PERMISSIONS.equals(intent.getExtras().getString("action")))
return;
String pkgName = intent.getExtras().getString("Package");
boolean killApp = intent.getExtras().getBoolean("Kill", false);
XposedMod.prefs.reload();
Object pkgInfo;
synchronized (mPackages) {
pkgInfo = mPackages.get(pkgName);
callMethod(pmSvc, "grantPermissionsLPw", pkgInfo, true);
callMethod(mSettings, "writeLPr");
}
// Apply new permissions if needed
if (killApp) {
try {
ApplicationInfo appInfo = (ApplicationInfo) getObjectField(pkgInfo, "applicationInfo");
if (Build.VERSION.SDK_INT <= 18)
callMethod(pmSvc, "killApplication", pkgName, appInfo.uid);
else
callMethod(pmSvc, "killApplication", pkgName, appInfo.uid, "apply App Settings");
} catch (Throwable t) {
XposedBridge.log(t);
}
}
} catch (Throwable t) {
XposedBridge.log(t);
}
}
}