1 /*
2  * Copyright (C) 2018 Google Inc.
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 package android.packageinstaller.uninstall.cts;
17 
18 import static android.app.AppOpsManager.MODE_ALLOWED;
19 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
20 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
21 import static android.graphics.PixelFormat.TRANSLUCENT;
22 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
23 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
24 
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assert.assertNull;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.pm.PackageManager;
34 import android.net.Uri;
35 import android.os.Handler;
36 import android.os.Looper;
37 import android.platform.test.annotations.AppModeFull;
38 import android.platform.test.annotations.AsbSecurityTest;
39 import android.support.test.uiautomator.By;
40 import android.support.test.uiautomator.UiDevice;
41 import android.support.test.uiautomator.UiObject2;
42 import android.support.test.uiautomator.Until;
43 import android.util.Log;
44 import android.view.Gravity;
45 import android.view.KeyEvent;
46 import android.view.LayoutInflater;
47 import android.view.View;
48 import android.view.WindowManager;
49 import android.view.WindowManager.LayoutParams;
50 
51 import androidx.test.InstrumentationRegistry;
52 import androidx.test.runner.AndroidJUnit4;
53 
54 import com.android.compatibility.common.util.AppOpsUtils;
55 
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.junit.runner.RunWith;
59 
60 import java.io.ByteArrayOutputStream;
61 import java.io.IOException;
62 import java.nio.charset.StandardCharsets;
63 
64 @RunWith(AndroidJUnit4.class)
65 @AppModeFull
66 public class UninstallTest {
67     private static final String LOG_TAG = UninstallTest.class.getSimpleName();
68 
69     private static final String TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts";
70 
71     private static final long TIMEOUT_MS = 30000;
72     private static final String APP_OP_STR = "REQUEST_DELETE_PACKAGES";
73 
74     private Context mContext;
75     private UiDevice mUiDevice;
76 
77     @Before
setup()78     public void setup() throws Exception {
79         mContext = InstrumentationRegistry.getTargetContext();
80 
81         // Unblock UI
82         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
83         if (!mUiDevice.isScreenOn()) {
84             mUiDevice.wakeUp();
85         }
86         mUiDevice.executeShellCommand("wm dismiss-keyguard");
87         AppOpsUtils.reset(mContext.getPackageName());
88     }
89 
dumpWindowHierarchy()90     private void dumpWindowHierarchy() throws InterruptedException, IOException {
91         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
92         mUiDevice.dumpWindowHierarchy(outputStream);
93         String windowHierarchy = outputStream.toString(StandardCharsets.UTF_8.name());
94 
95         Log.w(LOG_TAG, "Window hierarchy:");
96         for (String line : windowHierarchy.split("\n")) {
97             Thread.sleep(10);
98             Log.w(LOG_TAG, line);
99         }
100     }
101 
startUninstall()102     private void startUninstall() {
103         Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
104         intent.setData(Uri.parse("package:" + TEST_APK_PACKAGE_NAME));
105         intent.addFlags(FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK);
106         Log.d(LOG_TAG, "sending uninstall intent ("  + intent + ") on user " + mContext.getUser());
107         mContext.startActivity(intent);
108     }
109 
110     @Test
111     @AsbSecurityTest(cveBugId = 171221302)
overlaysAreSuppressedWhenConfirmingUninstall()112     public void overlaysAreSuppressedWhenConfirmingUninstall() throws Exception {
113         AppOpsUtils.setOpMode(mContext.getPackageName(), "SYSTEM_ALERT_WINDOW", MODE_ALLOWED);
114 
115         WindowManager windowManager = mContext.getSystemService(WindowManager.class);
116         LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT,
117                 TYPE_APPLICATION_OVERLAY, 0, TRANSLUCENT);
118         layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
119 
120         View[] overlay = new View[1];
121         new Handler(Looper.getMainLooper()).post(() -> {
122             overlay[0] = LayoutInflater.from(mContext).inflate(R.layout.overlay_activity,
123                     null);
124             windowManager.addView(overlay[0], layoutParams);
125         });
126 
127         try {
128             mUiDevice.wait(Until.findObject(By.res(mContext.getPackageName(),
129                     "overlay_description")), TIMEOUT_MS);
130 
131             startUninstall();
132 
133             long start = System.currentTimeMillis();
134             while (System.currentTimeMillis() - start < TIMEOUT_MS) {
135                 try {
136                     assertNull(mUiDevice.findObject(By.res(mContext.getPackageName(),
137                             "overlay_description")));
138                     return;
139                 } catch (Throwable e) {
140                     Thread.sleep(100);
141                 }
142             }
143 
144             fail();
145         } finally {
146             windowManager.removeView(overlay[0]);
147         }
148     }
149 
150     @Test
testUninstall()151     public void testUninstall() throws Exception {
152         assertTrue("Package is not installed", isInstalled());
153 
154         startUninstall();
155 
156         if (mUiDevice.wait(Until.findObject(By.text("Do you want to uninstall this app?")),
157                 TIMEOUT_MS) == null) {
158             dumpWindowHierarchy();
159         }
160         assertNotNull("Uninstall prompt not shown",
161                 mUiDevice.wait(Until.findObject(By.text("Do you want to uninstall this app?")),
162                         TIMEOUT_MS));
163         // The app's name should be shown to the user.
164         assertNotNull(mUiDevice.findObject(By.text("Empty Test App")));
165 
166         // Confirm uninstall
167         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
168             UiObject2 clickableView = mUiDevice
169                     .findObject(By.focusable(true).hasDescendant(By.text("OK")));
170             if (!clickableView.isFocused()) {
171                 mUiDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_DOWN);
172             }
173             for (int i = 0; i < 100; i++) {
174                 if (clickableView.isFocused()) {
175                     break;
176                 }
177                 Thread.sleep(100);
178             }
179             mUiDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
180         } else {
181             UiObject2 clickableView = mUiDevice.findObject(By.text("OK"));
182             if (clickableView == null) {
183               dumpWindowHierarchy();
184               fail("OK button not shown");
185             }
186             clickableView.click();
187         }
188 
189         for (int i = 0; i < 30; i++) {
190             // We can't detect the confirmation Toast with UiAutomator, so we'll poll
191             Thread.sleep(500);
192             if (!isInstalled()) {
193                 break;
194             }
195         }
196         assertFalse("Package wasn't uninstalled.", isInstalled());
197         assertTrue(AppOpsUtils.allowedOperationLogged(mContext.getPackageName(), APP_OP_STR));
198     }
199 
isInstalled()200     private boolean isInstalled() {
201         Log.d(LOG_TAG, "Testing if package " + TEST_APK_PACKAGE_NAME + " is installed for user "
202                 + mContext.getUser());
203         try {
204             mContext.getPackageManager().getPackageInfo(TEST_APK_PACKAGE_NAME, /* flags= */ 0);
205             return true;
206         } catch (PackageManager.NameNotFoundException e) {
207             Log.v(LOG_TAG, "Package " + TEST_APK_PACKAGE_NAME + " not installed for user "
208                     + mContext.getUser() + ": " + e);
209             return false;
210         }
211     }
212 }
213