Skip to content

Commit

Permalink
Implement access to settings without Context
Browse files Browse the repository at this point in the history
Expose methods to access the Android settings using private APIs.

This allows to read and write settings values immediately without
starting a new process to call "settings".
  • Loading branch information
rom1v committed May 1, 2020
1 parent 62c0c13 commit 8c67992
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 0 deletions.
5 changes: 5 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Device.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.genymobile.scrcpy;

import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl;
Expand Down Expand Up @@ -199,4 +200,8 @@ public void rotateDevice() {
wm.thawRotation();
}
}

public ContentProvider createSettingsProvider() {
return serviceManager.getActivityManager().createSettingsProvider();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.genymobile.scrcpy.wrappers;

import com.genymobile.scrcpy.Ln;

import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ActivityManager {

private final IInterface manager;
private Method getContentProviderExternalMethod;
private boolean getContentProviderExternalMethodLegacy;
private Method removeContentProviderExternalMethod;

public ActivityManager(IInterface manager) {
this.manager = manager;
}

private Method getGetContentProviderExternalMethod() throws NoSuchMethodException {
if (getContentProviderExternalMethod == null) {
try {
getContentProviderExternalMethod = manager.getClass()
.getMethod("getContentProviderExternal", String.class, int.class, IBinder.class, String.class);
} catch (NoSuchMethodException e) {
// old version
getContentProviderExternalMethod = manager.getClass().getMethod("getContentProviderExternal", String.class, int.class, IBinder.class);
getContentProviderExternalMethodLegacy = true;
}
}
return getContentProviderExternalMethod;
}

private Method getRemoveContentProviderExternalMethod() throws NoSuchMethodException {
if (removeContentProviderExternalMethod == null) {
removeContentProviderExternalMethod = manager.getClass().getMethod("removeContentProviderExternal", String.class, IBinder.class);
}
return removeContentProviderExternalMethod;
}

private ContentProvider getContentProviderExternal(String name, IBinder token) {
try {
Method method = getGetContentProviderExternalMethod();
Object[] args;
if (!getContentProviderExternalMethodLegacy) {
// new version
args = new Object[]{name, ServiceManager.USER_ID, token, null};
} else {
// old version
args = new Object[]{name, ServiceManager.USER_ID, token};
}
// ContentProviderHolder providerHolder = getContentProviderExternal(...);
Object providerHolder = method.invoke(manager, args);
if (providerHolder == null) {
return null;
}
// IContentProvider provider = providerHolder.provider;
Field providerField = providerHolder.getClass().getDeclaredField("provider");
providerField.setAccessible(true);
Object provider = providerField.get(providerHolder);
if (provider == null) {
return null;
}
return new ContentProvider(this, provider, name, token);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | NoSuchFieldException e) {
Ln.e("Could not invoke method", e);
return null;
}
}

void removeContentProviderExternal(String name, IBinder token) {
try {
Method method = getRemoveContentProviderExternalMethod();
method.invoke(manager, name, token);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);
}
}

public ContentProvider createSettingsProvider() {
return getContentProviderExternal("settings", new Binder());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.genymobile.scrcpy.wrappers;

import com.genymobile.scrcpy.Ln;

import android.os.Bundle;
import android.os.IBinder;

import java.io.Closeable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ContentProvider implements Closeable {

public static final String TABLE_SYSTEM = "system";
public static final String TABLE_SECURE = "secure";
public static final String TABLE_GLOBAL = "global";

// See android/providerHolder/Settings.java
private static final String CALL_METHOD_GET_SYSTEM = "GET_system";
private static final String CALL_METHOD_GET_SECURE = "GET_secure";
private static final String CALL_METHOD_GET_GLOBAL = "GET_global";

private static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
private static final String CALL_METHOD_PUT_SECURE = "PUT_secure";
private static final String CALL_METHOD_PUT_GLOBAL = "PUT_global";

private static final String CALL_METHOD_USER_KEY = "_user";

private static final String NAME_VALUE_TABLE_VALUE = "value";

private final ActivityManager manager;
// android.content.IContentProvider
private final Object provider;
private final String name;
private final IBinder token;

private Method callMethod;
private boolean callMethodLegacy;

ContentProvider(ActivityManager manager, Object provider, String name, IBinder token) {
this.manager = manager;
this.provider = provider;
this.name = name;
this.token = token;
}

private Method getCallMethod() throws NoSuchMethodException {
if (callMethod == null) {
try {
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, String.class, Bundle.class);
} catch (NoSuchMethodException e) {
// old version
callMethod = provider.getClass().getMethod("call", String.class, String.class, String.class, Bundle.class);
callMethodLegacy = true;
}
}
return callMethod;
}

private Bundle call(String callMethod, String arg, Bundle extras) {
try {
Method method = getCallMethod();
Object[] args;
if (!callMethodLegacy) {
args = new Object[]{ServiceManager.PACKAGE_NAME, "settings", callMethod, arg, extras};
} else {
args = new Object[]{ServiceManager.PACKAGE_NAME, callMethod, arg, extras};
}
return (Bundle) method.invoke(provider, args);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
Ln.e("Could not invoke method", e);
return null;
}
}

public void close() {
manager.removeContentProviderExternal(name, token);
}

private static String getGetMethod(String table) {
switch (table) {
case TABLE_SECURE:
return CALL_METHOD_GET_SECURE;
case TABLE_SYSTEM:
return CALL_METHOD_GET_SYSTEM;
case TABLE_GLOBAL:
return CALL_METHOD_GET_GLOBAL;
default:
throw new IllegalArgumentException("Invalid table: " + table);
}
}

private static String getPutMethod(String table) {
switch (table) {
case TABLE_SECURE:
return CALL_METHOD_PUT_SECURE;
case TABLE_SYSTEM:
return CALL_METHOD_PUT_SYSTEM;
case TABLE_GLOBAL:
return CALL_METHOD_PUT_GLOBAL;
default:
throw new IllegalArgumentException("Invalid table: " + table);
}
}

public String getValue(String table, String key) {
String method = getGetMethod(table);
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
Bundle bundle = call(method, key, arg);
if (bundle == null) {
return null;
}
return bundle.getString("value");
}

public void putValue(String table, String key, String value) {
String method = getPutMethod(table);
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, ServiceManager.USER_ID);
arg.putString(NAME_VALUE_TABLE_VALUE, value);
call(method, key, arg);
}

public String getAndPutValue(String table, String key, String value) {
String oldValue = getValue(table, key);
if (!value.equals(oldValue)) {
putValue(table, key, value);
}
return oldValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public final class ServiceManager {
private PowerManager powerManager;
private StatusBarManager statusBarManager;
private ClipboardManager clipboardManager;
private ActivityManager activityManager;

public ServiceManager() {
try {
Expand Down Expand Up @@ -76,4 +77,21 @@ public ClipboardManager getClipboardManager() {
}
return clipboardManager;
}

public ActivityManager getActivityManager() {
if (activityManager == null) {
try {
// On old Android versions, the ActivityManager is not exposed via AIDL,
// so use ActivityManagerNative.getDefault()
Class<?> cls = Class.forName("android.app.ActivityManagerNative");
Method getDefaultMethod = cls.getDeclaredMethod("getDefault");
IInterface am = (IInterface) getDefaultMethod.invoke(null);
activityManager = new ActivityManager(am);
} catch (Exception e) {
throw new AssertionError(e);
}
}

return activityManager;
}
}

0 comments on commit 8c67992

Please sign in to comment.