• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.textclassifier.testing;
18 
19 import android.app.UiAutomation;
20 import android.content.pm.PackageManager;
21 import android.content.pm.PackageManager.NameNotFoundException;
22 import android.provider.DeviceConfig;
23 import android.util.Log;
24 import android.view.textclassifier.TextClassificationManager;
25 import android.view.textclassifier.TextClassifier;
26 import androidx.test.core.app.ApplicationProvider;
27 import androidx.test.platform.app.InstrumentationRegistry;
28 import com.android.compatibility.common.util.DeviceConfigStateHelper;
29 import com.google.common.io.ByteStreams;
30 import java.io.FileInputStream;
31 import java.io.IOException;
32 import org.junit.rules.ExternalResource;
33 
34 /** A rule that manages a text classifier that is backed by the ExtServices. */
35 public final class ExtServicesTextClassifierRule extends ExternalResource {
36   private static final String TAG = "androidtc";
37   private static final String CONFIG_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE =
38       "textclassifier_service_package_override";
39   private static final String PKG_NAME_GOOGLE_EXTSERVICES = "com.google.android.ext.services";
40   private static final String PKG_NAME_AOSP_EXTSERVICES = "android.ext.services";
41 
42   private UiAutomation uiAutomation;
43   private DeviceConfig.Properties originalProperties;
44   private DeviceConfig.Properties.Builder newPropertiesBuilder;
45 
46   @Override
before()47   protected void before() throws Exception {
48     uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
49     uiAutomation.adoptShellPermissionIdentity();
50     originalProperties = DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TEXTCLASSIFIER);
51     newPropertiesBuilder =
52         new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_TEXTCLASSIFIER)
53             .setString(
54                 CONFIG_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE, getExtServicesPackageName());
55     overrideDeviceConfig();
56   }
57 
58   @Override
after()59   protected void after() {
60     try {
61       DeviceConfigStateHelper.callWithSyncEnabledWithShellPermissions(() ->
62           DeviceConfig.setProperties(originalProperties));
63     } catch (Throwable t) {
64       Log.e(TAG, "Failed to reset DeviceConfig", t);
65     } finally {
66       uiAutomation.dropShellPermissionIdentity();
67     }
68   }
69 
addDeviceConfigOverride(String name, String value)70   public void addDeviceConfigOverride(String name, String value) {
71     newPropertiesBuilder.setString(name, value);
72   }
73 
74   /**
75    * Overrides the TextClassifier DeviceConfig manually.
76    *
77    * <p>This will clean up all device configs not in newPropertiesBuilder.
78    *
79    * <p>We will need to call this everytime before testing, because DeviceConfig can be synced in
80    * background at anytime. DeviceConfig#setSyncDisabledMode is to disable sync, however it's a
81    * hidden API.
82    */
overrideDeviceConfig()83   public void overrideDeviceConfig() throws Exception {
84     DeviceConfigStateHelper.callWithSyncEnabledWithShellPermissions(() ->
85         DeviceConfig.setProperties(newPropertiesBuilder.build()));
86   }
87 
88   /** Force stop ExtServices. Force-stop-and-start can be helpful to reload some states. */
forceStopExtServices()89   public void forceStopExtServices() {
90     runShellCommand("am force-stop com.google.android.ext.services");
91     runShellCommand("am force-stop android.ext.services");
92   }
93 
getTextClassifier()94   public TextClassifier getTextClassifier() {
95     TextClassificationManager textClassificationManager =
96         ApplicationProvider.getApplicationContext()
97             .getSystemService(TextClassificationManager.class);
98     textClassificationManager.setTextClassifier(null); // Reset TC overrides
99     return textClassificationManager.getTextClassifier();
100   }
101 
dumpDefaultTextClassifierService()102   public void dumpDefaultTextClassifierService() {
103     runShellCommand(
104         "dumpsys activity service com.google.android.ext.services/"
105             + "com.android.textclassifier.DefaultTextClassifierService");
106     runShellCommand("cmd device_config list textclassifier");
107   }
108 
enableVerboseLogging()109   public void enableVerboseLogging() {
110     runShellCommand("setprop log.tag.androidtc VERBOSE");
111   }
112 
runShellCommand(String cmd)113   private void runShellCommand(String cmd) {
114     Log.v(TAG, "run shell command: " + cmd);
115     try (FileInputStream output =
116         new FileInputStream(uiAutomation.executeShellCommand(cmd).getFileDescriptor())) {
117       String cmdOutput = new String(ByteStreams.toByteArray(output));
118       if (!cmdOutput.isEmpty()) {
119         Log.d(TAG, "cmd output: " + cmdOutput);
120       }
121     } catch (IOException ioe) {
122       Log.w(TAG, "failed to get cmd output", ioe);
123     }
124   }
125 
getExtServicesPackageName()126   private static String getExtServicesPackageName() {
127     PackageManager packageManager = ApplicationProvider.getApplicationContext().getPackageManager();
128     try {
129       packageManager.getApplicationInfo(PKG_NAME_GOOGLE_EXTSERVICES, /* flags= */ 0);
130       return PKG_NAME_GOOGLE_EXTSERVICES;
131     } catch (NameNotFoundException e) {
132       return PKG_NAME_AOSP_EXTSERVICES;
133     }
134   }
135 }
136