Add binder implementation to support device config shell commands from the
command line.
Test: atest SettingsProviderTest:DeviceConfigServiceTest
Further tested manually from the command line
Bug:109919982
Bug:113101834
Change-Id: I62da11f8be9d24a2e304c10592d689ae007eb7ec
This commit is contained in:
10
cmds/device_config/Android.mk
Normal file
10
cmds/device_config/Android.mk
Normal file
@@ -0,0 +1,10 @@
|
||||
# Copyright 2018 The Android Open Source Project
|
||||
#
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := device_config
|
||||
LOCAL_SRC_FILES := device_config
|
||||
LOCAL_MODULE_CLASS := EXECUTABLES
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
include $(BUILD_PREBUILT)
|
||||
2
cmds/device_config/device_config
Executable file
2
cmds/device_config/device_config
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/system/bin/sh
|
||||
cmd device_config "$@"
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.providers.settings;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.IContentProvider;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.os.ShellCallback;
|
||||
import android.os.ShellCommand;
|
||||
import android.provider.Settings;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Receives shell commands from the command line related to device config flags, and dispatches them
|
||||
* to the SettingsProvider.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class DeviceConfigService extends Binder {
|
||||
/**
|
||||
* TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
|
||||
* API.
|
||||
*/
|
||||
private static final Uri CONFIG_CONTENT_URI =
|
||||
Uri.parse("content://" + Settings.AUTHORITY + "/config");
|
||||
|
||||
final SettingsProvider mProvider;
|
||||
|
||||
public DeviceConfigService(SettingsProvider provider) {
|
||||
mProvider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
|
||||
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
|
||||
(new MyShellCommand(mProvider)).exec(this, in, out, err, args, callback, resultReceiver);
|
||||
}
|
||||
|
||||
static final class MyShellCommand extends ShellCommand {
|
||||
final SettingsProvider mProvider;
|
||||
|
||||
enum CommandVerb {
|
||||
UNSPECIFIED,
|
||||
GET,
|
||||
PUT,
|
||||
DELETE,
|
||||
LIST,
|
||||
RESET,
|
||||
}
|
||||
|
||||
MyShellCommand(SettingsProvider provider) {
|
||||
mProvider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(String cmd) {
|
||||
if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
|
||||
onHelp();
|
||||
return -1;
|
||||
}
|
||||
|
||||
final PrintWriter perr = getErrPrintWriter();
|
||||
boolean isValid = false;
|
||||
CommandVerb verb;
|
||||
if ("get".equalsIgnoreCase(cmd)) {
|
||||
verb = CommandVerb.GET;
|
||||
} else if ("put".equalsIgnoreCase(cmd)) {
|
||||
verb = CommandVerb.PUT;
|
||||
} else if ("delete".equalsIgnoreCase(cmd)) {
|
||||
verb = CommandVerb.DELETE;
|
||||
} else if ("list".equalsIgnoreCase(cmd)) {
|
||||
verb = CommandVerb.LIST;
|
||||
if (peekNextArg() == null) {
|
||||
isValid = true;
|
||||
}
|
||||
} else if ("reset".equalsIgnoreCase(cmd)) {
|
||||
verb = CommandVerb.RESET;
|
||||
} else {
|
||||
// invalid
|
||||
perr.println("Invalid command: " + cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int resetMode = -1;
|
||||
boolean makeDefault = false;
|
||||
String namespace = null;
|
||||
String key = null;
|
||||
String value = null;
|
||||
String arg = null;
|
||||
while ((arg = getNextArg()) != null) {
|
||||
if (verb == CommandVerb.RESET) {
|
||||
if (resetMode == -1) {
|
||||
if ("untrusted_defaults".equalsIgnoreCase(arg)) {
|
||||
resetMode = Settings.RESET_MODE_UNTRUSTED_DEFAULTS;
|
||||
} else if ("untrusted_clear".equalsIgnoreCase(arg)) {
|
||||
resetMode = Settings.RESET_MODE_UNTRUSTED_CHANGES;
|
||||
} else if ("trusted_defaults".equalsIgnoreCase(arg)) {
|
||||
resetMode = Settings.RESET_MODE_TRUSTED_DEFAULTS;
|
||||
} else {
|
||||
// invalid
|
||||
perr.println("Invalid reset mode: " + arg);
|
||||
return -1;
|
||||
}
|
||||
if (peekNextArg() == null) {
|
||||
isValid = true;
|
||||
}
|
||||
} else {
|
||||
namespace = arg;
|
||||
if (peekNextArg() == null) {
|
||||
isValid = true;
|
||||
} else {
|
||||
// invalid
|
||||
perr.println("Too many arguments");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else if (namespace == null) {
|
||||
namespace = arg;
|
||||
if (verb == CommandVerb.LIST) {
|
||||
if (peekNextArg() == null) {
|
||||
isValid = true;
|
||||
} else {
|
||||
// invalid
|
||||
perr.println("Too many arguments");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else if (key == null) {
|
||||
key = arg;
|
||||
if ((verb == CommandVerb.GET || verb == CommandVerb.DELETE)) {
|
||||
if (peekNextArg() == null) {
|
||||
isValid = true;
|
||||
} else {
|
||||
// invalid
|
||||
perr.println("Too many arguments");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else if (value == null) {
|
||||
value = arg;
|
||||
if (verb == CommandVerb.PUT && peekNextArg() == null) {
|
||||
isValid = true;
|
||||
}
|
||||
} else if ("default".equalsIgnoreCase(arg)) {
|
||||
makeDefault = true;
|
||||
if (verb == CommandVerb.PUT && peekNextArg() == null) {
|
||||
isValid = true;
|
||||
} else {
|
||||
// invalid
|
||||
perr.println("Too many arguments");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
perr.println("Bad arguments");
|
||||
return -1;
|
||||
}
|
||||
|
||||
final IContentProvider iprovider = mProvider.getIContentProvider();
|
||||
final PrintWriter pout = getOutPrintWriter();
|
||||
switch (verb) {
|
||||
case GET:
|
||||
pout.println(get(iprovider, namespace, key));
|
||||
break;
|
||||
case PUT:
|
||||
put(iprovider, namespace, key, value, makeDefault);
|
||||
break;
|
||||
case DELETE:
|
||||
pout.println(delete(iprovider, namespace, key)
|
||||
? "Successfully deleted " + key + " from " + namespace
|
||||
: "Failed to delete " + key + " from " + namespace);
|
||||
break;
|
||||
case LIST:
|
||||
for (String line : list(iprovider, namespace)) {
|
||||
pout.println(line);
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
reset(iprovider, resetMode, namespace);
|
||||
break;
|
||||
default:
|
||||
perr.println("Unspecified command");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHelp() {
|
||||
PrintWriter pw = getOutPrintWriter();
|
||||
pw.println("Device Config (device_config) commands:");
|
||||
pw.println(" help");
|
||||
pw.println(" Print this help text.");
|
||||
pw.println(" get NAMESPACE KEY");
|
||||
pw.println(" Retrieve the current value of KEY from the given NAMESPACE.");
|
||||
pw.println(" put NAMESPACE KEY VALUE [default]");
|
||||
pw.println(" Change the contents of KEY to VALUE for the given NAMESPACE.");
|
||||
pw.println(" {default} to set as the default value.");
|
||||
pw.println(" delete NAMESPACE KEY");
|
||||
pw.println(" Delete the entry for KEY for the given NAMESPACE.");
|
||||
pw.println(" list [NAMESPACE]");
|
||||
pw.println(" Print all keys and values defined, optionally for the given "
|
||||
+ "NAMESPACE.");
|
||||
pw.println(" reset RESET_MODE [NAMESPACE]");
|
||||
pw.println(" Reset all flag values, optionally for a NAMESPACE, according to "
|
||||
+ "RESET_MODE.");
|
||||
pw.println(" RESET_MODE is one of {untrusted_defaults, untrusted_clear, "
|
||||
+ "trusted_defaults}");
|
||||
pw.println(" NAMESPACE limits which flags are reset if provided, otherwise all "
|
||||
+ "flags are reset");
|
||||
}
|
||||
|
||||
private String get(IContentProvider provider, String namespace, String key) {
|
||||
String compositeKey = namespace + "/" + key;
|
||||
String result = null;
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(Settings.CALL_METHOD_USER_KEY,
|
||||
ActivityManager.getService().getCurrentUser().id);
|
||||
Bundle b = provider.call(resolveCallingPackage(), Settings.CALL_METHOD_GET_CONFIG,
|
||||
compositeKey, args);
|
||||
if (b != null) {
|
||||
result = b.getPairValue();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Failed in IPC", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void put(IContentProvider provider, String namespace, String key, String value,
|
||||
boolean makeDefault) {
|
||||
String compositeKey = namespace + "/" + key;
|
||||
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Settings.NameValueTable.VALUE, value);
|
||||
args.putInt(Settings.CALL_METHOD_USER_KEY,
|
||||
ActivityManager.getService().getCurrentUser().id);
|
||||
if (makeDefault) {
|
||||
args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
|
||||
}
|
||||
provider.call(resolveCallingPackage(), Settings.CALL_METHOD_PUT_CONFIG,
|
||||
compositeKey, args);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Failed in IPC", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean delete(IContentProvider provider, String namespace, String key) {
|
||||
String compositeKey = namespace + "/" + key;
|
||||
boolean success;
|
||||
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(Settings.CALL_METHOD_USER_KEY,
|
||||
ActivityManager.getService().getCurrentUser().id);
|
||||
Bundle b = provider.call(resolveCallingPackage(),
|
||||
Settings.CALL_METHOD_DELETE_CONFIG, compositeKey, args);
|
||||
success = (b != null && b.getInt(SettingsProvider.RESULT_ROWS_DELETED) == 1);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Failed in IPC", e);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private List<String> list(IContentProvider provider, @Nullable String namespace) {
|
||||
final ArrayList<String> lines = new ArrayList<>();
|
||||
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(Settings.CALL_METHOD_USER_KEY,
|
||||
ActivityManager.getService().getCurrentUser().id);
|
||||
if (namespace != null) {
|
||||
args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace);
|
||||
}
|
||||
Bundle b = provider.call(resolveCallingPackage(),
|
||||
Settings.CALL_METHOD_LIST_CONFIG, null, args);
|
||||
if (b != null) {
|
||||
Map<String, String> flagsToValues =
|
||||
(HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
|
||||
for (String key : flagsToValues.keySet()) {
|
||||
lines.add(key + "=" + flagsToValues.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(lines);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Failed in IPC", e);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private void reset(IContentProvider provider, int resetMode, @Nullable String namespace) {
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(Settings.CALL_METHOD_USER_KEY,
|
||||
ActivityManager.getService().getCurrentUser().id);
|
||||
args.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, resetMode);
|
||||
args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace);
|
||||
provider.call(
|
||||
resolveCallingPackage(), Settings.CALL_METHOD_RESET_CONFIG, null, args);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Failed in IPC", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String resolveCallingPackage() {
|
||||
switch (Binder.getCallingUid()) {
|
||||
case Process.ROOT_UID: {
|
||||
return "root";
|
||||
}
|
||||
|
||||
case Process.SHELL_UID: {
|
||||
return "com.android.shell";
|
||||
}
|
||||
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,6 +335,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
startWatchingUserRestrictionChanges();
|
||||
});
|
||||
ServiceManager.addService("settings", new SettingsService(this));
|
||||
ServiceManager.addService("device_config", new DeviceConfigService(this));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.providers.settings;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import libcore.io.Streams;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Tests for {@link DeviceConfigService}.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DeviceConfigServiceTest {
|
||||
/**
|
||||
* TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
|
||||
* API.
|
||||
*/
|
||||
private static final Uri CONFIG_CONTENT_URI =
|
||||
Uri.parse("content://" + Settings.AUTHORITY + "/config");
|
||||
private static final String sNamespace = "namespace1";
|
||||
private static final String sKey = "key1";
|
||||
private static final String sValue = "value1";
|
||||
|
||||
private ContentResolver mContentResolver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
deleteFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() throws Exception {
|
||||
final String newNamespace = "namespace2";
|
||||
final String newValue = "value2";
|
||||
|
||||
String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
assertNull(result);
|
||||
|
||||
try {
|
||||
executeShellCommand("device_config put " + sNamespace + " " + sKey + " " + sValue);
|
||||
executeShellCommand("device_config put " + newNamespace + " " + sKey + " " + newValue);
|
||||
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
assertEquals(sValue, result);
|
||||
result = getFromContentProvider(mContentResolver, newNamespace, sKey);
|
||||
assertEquals(newValue, result);
|
||||
} finally {
|
||||
deleteFromContentProvider(mContentResolver, newNamespace, sKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut_invalidArgs() throws Exception {
|
||||
// missing sNamespace
|
||||
executeShellCommand("device_config put " + sKey + " " + sValue);
|
||||
String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
// still null
|
||||
assertNull(result);
|
||||
|
||||
// too many arguments
|
||||
executeShellCommand(
|
||||
"device_config put " + sNamespace + " " + sKey + " " + sValue + " extra_arg");
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
// still null
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() throws Exception {
|
||||
final String newNamespace = "namespace2";
|
||||
|
||||
putWithContentProvider(mContentResolver, sNamespace, sKey, sValue);
|
||||
putWithContentProvider(mContentResolver, newNamespace, sKey, sValue);
|
||||
String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
assertEquals(sValue, result);
|
||||
result = getFromContentProvider(mContentResolver, newNamespace, sKey);
|
||||
assertEquals(sValue, result);
|
||||
|
||||
try {
|
||||
executeShellCommand("device_config delete " + sNamespace + " " + sKey);
|
||||
// sKey is deleted from sNamespace
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
assertNull(result);
|
||||
// sKey is not deleted from newNamespace
|
||||
result = getFromContentProvider(mContentResolver, newNamespace, sKey);
|
||||
assertEquals(sValue, result);
|
||||
} finally {
|
||||
deleteFromContentProvider(mContentResolver, newNamespace, sKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete_invalidArgs() throws Exception {
|
||||
putWithContentProvider(mContentResolver, sNamespace, sKey, sValue);
|
||||
String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
assertEquals(sValue, result);
|
||||
|
||||
// missing sNamespace
|
||||
executeShellCommand("device_config delete " + sKey);
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
// sValue was not deleted
|
||||
assertEquals(sValue, result);
|
||||
|
||||
// too many arguments
|
||||
executeShellCommand("device_config delete " + sNamespace + " " + sKey + " extra_arg");
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
// sValue was not deleted
|
||||
assertEquals(sValue, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReset_setUntrustedDefault() throws Exception {
|
||||
String newValue = "value2";
|
||||
|
||||
// make sValue the untrusted default (set by root)
|
||||
executeShellCommand(
|
||||
"device_config put " + sNamespace + " " + sKey + " " + sValue + " default");
|
||||
// make newValue the current value
|
||||
executeShellCommand(
|
||||
"device_config put " + sNamespace + " " + sKey + " " + newValue);
|
||||
String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
assertEquals(newValue, result);
|
||||
|
||||
executeShellCommand("device_config reset untrusted_defaults " + sNamespace);
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
// back to the default
|
||||
assertEquals(sValue, result);
|
||||
|
||||
executeShellCommand("device_config reset trusted_defaults " + sNamespace);
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
// not trusted default was set
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReset_setTrustedDefault() throws Exception {
|
||||
String newValue = "value2";
|
||||
|
||||
// make sValue the trusted default (set by system)
|
||||
putWithContentProvider(mContentResolver, sNamespace, sKey, sValue, true);
|
||||
// make newValue the current value
|
||||
executeShellCommand(
|
||||
"device_config put " + sNamespace + " " + sKey + " " + newValue);
|
||||
String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
assertEquals(newValue, result);
|
||||
|
||||
executeShellCommand("device_config reset untrusted_defaults " + sNamespace);
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
// back to the default
|
||||
assertEquals(sValue, result);
|
||||
|
||||
executeShellCommand("device_config reset trusted_defaults " + sNamespace);
|
||||
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
|
||||
// our trusted default is still set
|
||||
assertEquals(sValue, result);
|
||||
}
|
||||
|
||||
private static void executeShellCommand(String command) throws IOException {
|
||||
InputStream is = new FileInputStream(InstrumentationRegistry.getInstrumentation()
|
||||
.getUiAutomation().executeShellCommand(command).getFileDescriptor());
|
||||
Streams.readFully(is);
|
||||
}
|
||||
|
||||
private static void putWithContentProvider(ContentResolver resolver, String namespace,
|
||||
String key, String value) {
|
||||
putWithContentProvider(resolver, namespace, key, value, false);
|
||||
}
|
||||
|
||||
private static void putWithContentProvider(ContentResolver resolver, String namespace,
|
||||
String key, String value, boolean makeDefault) {
|
||||
String compositeName = namespace + "/" + key;
|
||||
Bundle args = new Bundle();
|
||||
args.putString(Settings.NameValueTable.VALUE, value);
|
||||
if (makeDefault) {
|
||||
args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
|
||||
}
|
||||
resolver.call(
|
||||
CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
|
||||
}
|
||||
|
||||
private static String getFromContentProvider(ContentResolver resolver, String namespace,
|
||||
String key) {
|
||||
String compositeName = namespace + "/" + key;
|
||||
Bundle result = resolver.call(
|
||||
CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
|
||||
assertNotNull(result);
|
||||
return result.getString(Settings.NameValueTable.VALUE);
|
||||
}
|
||||
|
||||
private static boolean deleteFromContentProvider(ContentResolver resolver, String namespace,
|
||||
String key) {
|
||||
String compositeName = namespace + "/" + key;
|
||||
Bundle result = resolver.call(
|
||||
CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
|
||||
assertNotNull(result);
|
||||
return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user