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