-
-
Notifications
You must be signed in to change notification settings - Fork 10.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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".
- Loading branch information
Showing
4 changed files
with
242 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
server/src/main/java/com/genymobile/scrcpy/wrappers/ActivityManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
132 changes: 132 additions & 0 deletions
132
server/src/main/java/com/genymobile/scrcpy/wrappers/ContentProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters