Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read/write settings via command on Android >= 12 #2802

Merged
merged 7 commits into from
Nov 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions server/src/main/java/com/genymobile/scrcpy/CleanUp.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.genymobile.scrcpy;

import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager;

import android.os.Parcel;
Expand Down Expand Up @@ -166,14 +165,21 @@ public static void main(String... args) {

if (config.disableShowTouches || config.restoreStayOn != -1) {
ServiceManager serviceManager = new ServiceManager();
try (ContentProvider settings = serviceManager.getActivityManager().createSettingsProvider()) {
if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\"");
settings.putValue(ContentProvider.TABLE_SYSTEM, "show_touches", "0");
Settings settings = new Settings(serviceManager);
if (config.disableShowTouches) {
Ln.i("Disabling \"show touches\"");
try {
settings.putValue(Settings.TABLE_SYSTEM, "show_touches", "0");
} catch (SettingsException e) {
Ln.e("Could not restore \"show_touches\"", e);
}
if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\"");
settings.putValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
}
if (config.restoreStayOn != -1) {
Ln.i("Restoring \"stay awake\"");
try {
settings.putValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(config.restoreStayOn));
} catch (SettingsException e) {
Ln.e("Could not restore \"stay_on_while_plugged_in\"", e);
}
}
}
Expand Down
33 changes: 33 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Command.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.genymobile.scrcpy;

import java.io.IOException;
import java.util.Arrays;
import java.util.Scanner;

public final class Command {
private Command() {
// not instantiable
}

public static void exec(String... cmd) throws IOException, InterruptedException {
Process process = Runtime.getRuntime().exec(cmd);
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
}

public static String execReadLine(String... cmd) throws IOException, InterruptedException {
String result = null;
Process process = Runtime.getRuntime().exec(cmd);
Scanner scanner = new Scanner(process.getInputStream());
if (scanner.hasNextLine()) {
result = scanner.nextLine();
}
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
return result;
}
}
6 changes: 3 additions & 3 deletions server/src/main/java/com/genymobile/scrcpy/Device.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.genymobile.scrcpy;

import com.genymobile.scrcpy.wrappers.ClipboardManager;
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 @@ -29,6 +28,7 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;

private static final ServiceManager SERVICE_MANAGER = new ServiceManager();
private static final Settings SETTINGS = new Settings(SERVICE_MANAGER);

public interface RotationListener {
void onRotationChanged(int rotation);
Expand Down Expand Up @@ -296,7 +296,7 @@ public static void rotateDevice() {
}
}

public static ContentProvider createSettingsProvider() {
return SERVICE_MANAGER.getActivityManager().createSettingsProvider();
public static Settings getSettings() {
return SETTINGS;
}
}
11 changes: 9 additions & 2 deletions server/src/main/java/com/genymobile/scrcpy/Ln.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,20 @@ public static void i(String message) {
}
}

public static void w(String message) {
public static void w(String message, Throwable throwable) {
if (isEnabled(Level.WARN)) {
Log.w(TAG, message);
Log.w(TAG, message, throwable);
System.out.println(PREFIX + "WARN: " + message);
if (throwable != null) {
throwable.printStackTrace();
}
}
}

public static void w(String message) {
w(message, null);
}

public static void e(String message, Throwable throwable) {
if (isEnabled(Level.ERROR)) {
Log.e(TAG, message, throwable);
Expand Down
53 changes: 39 additions & 14 deletions server/src/main/java/com/genymobile/scrcpy/Server.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.genymobile.scrcpy;

import com.genymobile.scrcpy.wrappers.ContentProvider;

import android.graphics.Rect;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
Expand All @@ -19,24 +17,25 @@ private Server() {
// not instantiable
}

private static void scrcpy(Options options) throws IOException {
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
final Device device = new Device(options);
List<CodecOption> codecOptions = CodecOption.parse(options.getCodecOptions());

private static void initAndCleanUp(Options options) {
boolean mustDisableShowTouchesOnCleanUp = false;
int restoreStayOn = -1;
if (options.getShowTouches() || options.getStayAwake()) {
try (ContentProvider settings = Device.createSettingsProvider()) {
if (options.getShowTouches()) {
String oldValue = settings.getAndPutValue(ContentProvider.TABLE_SYSTEM, "show_touches", "1");
Settings settings = Device.getSettings();
if (options.getShowTouches()) {
try {
String oldValue = settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
// If "show touches" was disabled, it must be disabled back on clean up
mustDisableShowTouchesOnCleanUp = !"1".equals(oldValue);
} catch (SettingsException e) {
Ln.e("Could not change \"show_touches\"", e);
}
}

if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
String oldValue = settings.getAndPutValue(ContentProvider.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
try {
String oldValue = settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
try {
restoreStayOn = Integer.parseInt(oldValue);
if (restoreStayOn == stayOn) {
Expand All @@ -46,11 +45,25 @@ private static void scrcpy(Options options) throws IOException {
} catch (NumberFormatException e) {
restoreStayOn = 0;
}
} catch (SettingsException e) {
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
}
}
}

CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
try {
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
} catch (IOException e) {
Ln.e("Could not configure cleanup", e);
}
}

private static void scrcpy(Options options) throws IOException {
Ln.i("Device: " + Build.MANUFACTURER + " " + Build.MODEL + " (Android " + Build.VERSION.RELEASE + ")");
final Device device = new Device(options);
List<CodecOption> codecOptions = CodecOption.parse(options.getCodecOptions());

Thread initThread = startInitThread(options);

boolean tunnelForward = options.isTunnelForward();

Expand Down Expand Up @@ -82,6 +95,7 @@ public void onClipboardTextChanged(String text) {
// this is expected on close
Ln.d("Screen streaming stopped");
} finally {
initThread.interrupt();
if (controllerThread != null) {
controllerThread.interrupt();
}
Expand All @@ -92,6 +106,17 @@ public void onClipboardTextChanged(String text) {
}
}

private static Thread startInitThread(final Options options) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
initAndCleanUp(options);
}
});
thread.start();
return thread;
}

private static Thread startController(final Controller controller) {
Thread thread = new Thread(new Runnable() {
@Override
Expand Down
84 changes: 84 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Settings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.genymobile.scrcpy;

import com.genymobile.scrcpy.wrappers.ContentProvider;
import com.genymobile.scrcpy.wrappers.ServiceManager;

import android.os.Build;

import java.io.IOException;

public class Settings {

public static final String TABLE_SYSTEM = ContentProvider.TABLE_SYSTEM;
public static final String TABLE_SECURE = ContentProvider.TABLE_SECURE;
public static final String TABLE_GLOBAL = ContentProvider.TABLE_GLOBAL;

private final ServiceManager serviceManager;

public Settings(ServiceManager serviceManager) {
this.serviceManager = serviceManager;
}

private static void execSettingsPut(String table, String key, String value) throws SettingsException {
try {
Command.exec("settings", "put", table, key, value);
} catch (IOException | InterruptedException e) {
throw new SettingsException("put", table, key, value, e);
}
}

private static String execSettingsGet(String table, String key) throws SettingsException {
try {
return Command.execReadLine("settings", "get", table, key);
} catch (IOException | InterruptedException e) {
throw new SettingsException("get", table, key, null, e);
}
}

public String getValue(String table, String key) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
return provider.getValue(table, key);
} catch (SettingsException e) {
Ln.w("Could not get settings value via ContentProvider, fallback to settings process", e);
}
}

return execSettingsGet(table, key);
}

public void putValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
provider.putValue(table, key, value);
} catch (SettingsException e) {
Ln.w("Could not put settings value via ContentProvider, fallback to settings process", e);
}
}

execSettingsPut(table, key, value);
}

public String getAndPutValue(String table, String key, String value) throws SettingsException {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
// on Android >= 12, it always fails: <https://github.com/Genymobile/scrcpy/issues/2788>
try (ContentProvider provider = serviceManager.getActivityManager().createSettingsProvider()) {
String oldValue = provider.getValue(table, key);
if (!value.equals(oldValue)) {
provider.putValue(table, key, value);
}
return oldValue;
} catch (SettingsException e) {
Ln.w("Could not get and put settings value via ContentProvider, fallback to settings process", e);
}
}

String oldValue = getValue(table, key);
if (!value.equals(oldValue)) {
putValue(table, key, value);
}
return oldValue;
}
}
11 changes: 11 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/SettingsException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.genymobile.scrcpy;

public class SettingsException extends Exception {
private static String createMessage(String method, String table, String key, String value) {
return "Could not access settings: " + method + " " + table + " " + key + (value != null ? " " + value : "");
}

public SettingsException(String method, String table, String key, String value, Throwable cause) {
super(createMessage(method, table, key, value), cause);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.genymobile.scrcpy.wrappers;

import com.genymobile.scrcpy.Ln;
import com.genymobile.scrcpy.SettingsException;

import android.annotation.SuppressLint;
import android.os.Bundle;
Expand Down Expand Up @@ -87,7 +88,8 @@ private Object getAttributionSource()
return attributionSource;
}

private Bundle call(String callMethod, String arg, Bundle extras) {
private Bundle call(String callMethod, String arg, Bundle extras)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
try {
Method method = getCallMethod();
Object[] args;
Expand All @@ -108,7 +110,7 @@ private Bundle call(String callMethod, String arg, Bundle extras) {
return (Bundle) method.invoke(provider, args);
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException | InstantiationException e) {
Ln.e("Could not invoke method", e);
return null;
throw e;
}
}

Expand Down Expand Up @@ -142,30 +144,31 @@ private static String getPutMethod(String table) {
}
}

public String getValue(String table, String key) {
public String getValue(String table, String key) throws SettingsException {
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;
try {
Bundle bundle = call(method, key, arg);
if (bundle == null) {
return null;
}
return bundle.getString("value");
} catch (Exception e) {
throw new SettingsException(table, "get", key, null, e);
}
return bundle.getString("value");

}

public void putValue(String table, String key, String value) {
public void putValue(String table, String key, String value) throws SettingsException {
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);
try {
call(method, key, arg);
} catch (Exception e) {
throw new SettingsException(table, "put", key, value, e);
}
return oldValue;
}
}