[go: up one dir, main page]

Add an util to get and set Android Shared Preference files.

And a function to update gboad shared preferences.
Note that this requires root permission.

Bug: chromium:443782461
Change-Id: Ief475a258c8e6dad0c71874027b89f4e1c7d04f7
Reviewed-on: https://chromium-review.googlesource.com/c/catapult/+/7008791
Commit-Queue: Haiyang Pan <hypan@google.com>
Reviewed-by: Ben Pastene <bpastene@chromium.org>
diff --git a/devil/devil/android/device_utils.py b/devil/devil/android/device_utils.py
index 3e687ea..d87e8d7 100644
--- a/devil/devil/android/device_utils.py
+++ b/devil/devil/android/device_utils.py
@@ -39,6 +39,7 @@
 from devil.android.sdk import adb_wrapper
 from devil.android.sdk import intent
 from devil.android.sdk import keyevent
+from devil.android.sdk import shared_prefs
 from devil.android.sdk import version_codes
 from devil.utils import parallelizer
 from devil.utils import reraiser_thread
@@ -339,6 +340,11 @@
 #  * on non-hsum, main user is the system user (user 0)
 _USER_FLAG_MAIN = 0x00004000
 
+# A string template pointing to file that stores the gboard preference
+# for a given user.
+_GBOARD_PKG = 'com.google.android.inputmethod.latin'
+_GBOARD_PREF_FILENAME = f'{_GBOARD_PKG}_preferences.xml'
+
 
 # Namespaces for settings
 class SettingsNamespace:
@@ -3838,6 +3844,27 @@
     finally:
       self.SetEnforce(not enabled, timeout=timeout, retries=retries)
 
+  @contextlib.contextmanager
+  def GboardPreferences(self, timeout=None, retries=None):
+    """Get the gboard preferences.
+
+    For users to read or modify the desired preferences.
+
+    Args:
+      timeout: timeout in seconds
+      retries: number of retries
+
+    Returns:
+      A shared_prefs.SharedPrefs object
+    """
+    with shared_prefs.SharedPrefs(self,
+                                  _GBOARD_PKG,
+                                  _GBOARD_PREF_FILENAME,
+                                  user_id=self.GetCurrentUser(),
+                                  use_encrypted_path=True) as prefs:
+      yield prefs
+
+
   @decorators.WithTimeoutAndRetriesFromInstance()
   def GetWebViewProvider(self, timeout=None, retries=None):
     """Returns the webview_provider setting from global settings.
diff --git a/devil/devil/android/sdk/shared_prefs.py b/devil/devil/android/sdk/shared_prefs.py
index 0c77ae4..a4180ef 100644
--- a/devil/devil/android/sdk/shared_prefs.py
+++ b/devil/devil/android/sdk/shared_prefs.py
@@ -172,7 +172,13 @@
 
 
 class SharedPrefs(object):
-  def __init__(self, device, package, filename, use_encrypted_path=False):
+
+  def __init__(self,
+               device,
+               package,
+               filename,
+               user_id=0,
+               use_encrypted_path=False):
     """Helper object to read and update "Shared Prefs" of Android apps.
 
     Such files typically look like, e.g.:
@@ -201,6 +207,7 @@
       package: A string with the package name of the app that owns the shared
         preferences file.
       filename: A string with the name of the preferences file to read/write.
+      user_id: The id of the targeting user, as an int. Default to 0.
       use_encrypted_path: Whether to read and write to the shared prefs location
         in the device-encrypted path (/data/user_de) instead of the older,
         unencrypted path (/data/data). Only supported on N+, but falls back to
@@ -213,8 +220,8 @@
     self._filename = filename
     self._unencrypted_path = '/data/data/%s/shared_prefs/%s' % (package,
                                                                 filename)
-    self._encrypted_path = '/data/user_de/0/%s/shared_prefs/%s' % (package,
-                                                                   filename)
+    self._encrypted_path = '/data/user_de/%d/%s/shared_prefs/%s' % (
+        user_id, package, filename)
     self._path = self._unencrypted_path
     self._encrypted = use_encrypted_path
     if use_encrypted_path:
@@ -275,7 +282,7 @@
     A empty xml document, which may be modified and saved on |commit|, is
     created if the file does not already exist.
     """
-    if self._device.FileExists(self.path):
+    if self._device.PathExists(self.path, as_root=True):
       self._xml = ElementTree.fromstring(
           self._device.ReadFile(self.path, as_root=True))
       assert self._xml.tag == 'map'
diff --git a/devil/devil/android/sdk/shared_prefs_test.py b/devil/devil/android/sdk/shared_prefs_test.py
index 13cb282..8734afd 100755
--- a/devil/devil/android/sdk/shared_prefs_test.py
+++ b/devil/devil/android/sdk/shared_prefs_test.py
@@ -27,7 +27,7 @@
   if files is None:
     files = {}
 
-  def file_exists(path):
+  def path_exists(path, **_kwargs):
     return path in files
 
   def write_file(path, contents, **_kwargs):
@@ -37,7 +37,7 @@
     return files[path]
 
   device = mock.MagicMock(spec=device_utils.DeviceUtils)
-  device.FileExists = mock.Mock(side_effect=file_exists)
+  device.PathExists = mock.Mock(side_effect=path_exists)
   device.WriteFile = mock.Mock(side_effect=write_file)
   device.ReadFile = mock.Mock(side_effect=read_file)
   return device
@@ -102,17 +102,17 @@
         return_value=version_codes.LOLLIPOP_MR1)
     prefs = shared_prefs.SharedPrefs(self.device, 'com.some.package',
                                      'other_prefs.xml')
-    self.assertFalse(self.device.FileExists(prefs.path))  # file does not exist
+    self.assertFalse(self.device.PathExists(prefs.path))  # file does not exist
     prefs.Load()
     self.assertEqual(len(prefs), 0)  # file did not exist, collection is empty
     prefs.SetInt('magicNumber', 42)
     prefs.SetFloat('myMetric', 3.14)
     prefs.SetLong('bigNumner', 6000000000)
     prefs.SetStringSet('apps', ['gmail', 'chrome', 'music'])
-    self.assertFalse(self.device.FileExists(prefs.path))  # still does not exist
+    self.assertFalse(self.device.PathExists(prefs.path))  # still does not exist
     self.assertTrue(prefs.changed)
     prefs.Commit()
-    self.assertTrue(self.device.FileExists(prefs.path))  # should exist now
+    self.assertTrue(self.device.PathExists(prefs.path))  # should exist now
     self.device.KillAll.assert_called_once_with(
         prefs.package, exact=True, as_root=True, quiet=True)
     self.assertFalse(prefs.changed)
diff --git a/devil/devil/android/sdk/version_codes.py b/devil/devil/android/sdk/version_codes.py
index 35c3c6e..4d91b37 100644
--- a/devil/devil/android/sdk/version_codes.py
+++ b/devil/devil/android/sdk/version_codes.py
@@ -26,3 +26,4 @@
 TIRAMISU = 33
 UPSIDE_DOWN_CAKE = 34
 VANILLA_ICE_CREAM = 35
+BAKLAVA = 36