Implement access to settings without Context
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".
This commit is contained in:
parent
62c0c1321f
commit
8c6799297b
4 changed files with 242 additions and 0 deletions
|
@ -1,5 +1,6 @@
|
||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import com.genymobile.scrcpy.wrappers.ContentProvider;
|
||||||
import com.genymobile.scrcpy.wrappers.InputManager;
|
import com.genymobile.scrcpy.wrappers.InputManager;
|
||||||
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
import com.genymobile.scrcpy.wrappers.ServiceManager;
|
||||||
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
import com.genymobile.scrcpy.wrappers.SurfaceControl;
|
||||||
|
@ -199,4 +200,8 @@ public final class Device {
|
||||||
wm.thawRotation();
|
wm.thawRotation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ContentProvider createSettingsProvider() {
|
||||||
|
return serviceManager.getActivityManager().createSettingsProvider();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ public final class ServiceManager {
|
||||||
private PowerManager powerManager;
|
private PowerManager powerManager;
|
||||||
private StatusBarManager statusBarManager;
|
private StatusBarManager statusBarManager;
|
||||||
private ClipboardManager clipboardManager;
|
private ClipboardManager clipboardManager;
|
||||||
|
private ActivityManager activityManager;
|
||||||
|
|
||||||
public ServiceManager() {
|
public ServiceManager() {
|
||||||
try {
|
try {
|
||||||
|
@ -76,4 +77,21 @@ public final class ServiceManager {
|
||||||
}
|
}
|
||||||
return clipboardManager;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue