1 /* 2 * Copyright (C) 2022 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.tests.sdksandbox; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import android.app.ActivityManager; 22 import android.app.ApplicationExitInfo; 23 import android.app.sdksandbox.SdkSandboxManager; 24 import android.app.sdksandbox.testutils.EmptyActivity; 25 import android.app.sdksandbox.testutils.FakeLoadSdkCallback; 26 import android.app.sdksandbox.testutils.SdkLifecycleHelper; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 32 import android.os.Bundle; 33 import android.os.DropBoxManager; 34 import android.os.Process; 35 36 import androidx.test.ext.junit.rules.ActivityScenarioRule; 37 import androidx.test.platform.app.InstrumentationRegistry; 38 39 import com.android.tests.sdkprovider.crashtest.ICrashTestSdkApi; 40 41 import org.junit.After; 42 import org.junit.Before; 43 import org.junit.Rule; 44 import org.junit.Test; 45 46 import java.util.List; 47 import java.util.concurrent.CountDownLatch; 48 import java.util.concurrent.TimeUnit; 49 50 public class SdkSandboxMetricsTestApp { 51 52 private SdkSandboxManager mSdkSandboxManager; 53 private static final String SDK_PACKAGE = "com.android.tests.sdkprovider.crashtest"; 54 private static final String TAG_SYSTEM_APP_CRASH = "system_app_crash"; 55 private static final int N_DROPBOX_HEADER_BYTES = 1024; 56 57 // Values declared in SdkSandboxMetricsTestAppManifest.xml 58 private static final String PACKAGE_NAME = "com.android.tests.sdksandbox"; 59 private static final String VERSION_NAME = "1.0"; 60 private static final int VERSION_CODE = 1; 61 62 // Values declared in CrashTestSdkProviderManifest.xml 63 private static final String SDK_PACKAGE_NAME = "com.android.tests.sdkprovider.crashtest"; 64 private static final int SDK_VERSION_CODE = 1; 65 66 @Rule public final ActivityScenarioRule mRule = new ActivityScenarioRule<>(EmptyActivity.class); 67 68 private DropBoxManager mDropboxManager; 69 private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); 70 private final SdkLifecycleHelper mSdkLifecycleHelper = new SdkLifecycleHelper(mContext); 71 private PackageManager mPackageManager; 72 private String mCrashEntryText; 73 74 @Before setup()75 public void setup() { 76 mSdkSandboxManager = mContext.getSystemService(SdkSandboxManager.class); 77 mDropboxManager = mContext.getSystemService(DropBoxManager.class); 78 mPackageManager = mContext.getPackageManager(); 79 assertThat(mSdkSandboxManager).isNotNull(); 80 81 // Unload the SDK before running tests to ensure that the SDK is not loaded before running a 82 // test 83 mSdkLifecycleHelper.unloadSdk(SDK_PACKAGE); 84 } 85 86 @After tearDown()87 public void tearDown() { 88 if (mSdkSandboxManager != null) { 89 mSdkLifecycleHelper.unloadSdk(SDK_PACKAGE); 90 } 91 } 92 93 @Test testSdkCanAccessSdkSandboxExitReasons()94 public void testSdkCanAccessSdkSandboxExitReasons() throws Exception { 95 mRule.getScenario(); 96 final long timeStampBeforeCrash = System.currentTimeMillis(); 97 generateSdkSandboxCrash(); 98 99 // Restart the SDK sandbox 100 final FakeLoadSdkCallback callback = new FakeLoadSdkCallback(); 101 mSdkSandboxManager.loadSdk(SDK_PACKAGE, new Bundle(), Runnable::run, callback); 102 callback.assertLoadSdkIsSuccessful(); 103 104 final ICrashTestSdkApi sdk = 105 ICrashTestSdkApi.Stub.asInterface(callback.getSandboxedSdk().getInterface()); 106 final List<ApplicationExitInfo> exitInfos = sdk.getSdkSandboxExitReasons(); 107 ApplicationExitInfo mostRecentExitInfo = exitInfos.get(0); 108 assertThat(mostRecentExitInfo).isNotNull(); 109 assertThat(mostRecentExitInfo.getRealUid()) 110 .isEqualTo(Process.toSdkSandboxUid(Process.myUid())); 111 assertThat(mostRecentExitInfo.getReason()).isEqualTo(ApplicationExitInfo.REASON_CRASH); 112 assertThat(mostRecentExitInfo.getTimestamp()).isGreaterThan(timeStampBeforeCrash); 113 } 114 115 @Test testAppWithDumpPermissionCanAccessSdkSandboxExitReasons()116 public void testAppWithDumpPermissionCanAccessSdkSandboxExitReasons() throws Exception { 117 mRule.getScenario(); 118 final long timeStampBeforeCrash = System.currentTimeMillis(); 119 generateSdkSandboxCrash(); 120 121 final ActivityManager am = mContext.getSystemService(ActivityManager.class); 122 assertThat(am).isNotNull(); 123 124 final ApplicationExitInfo info = 125 am.getHistoricalProcessExitReasons( 126 mContext.getPackageManager().getSdkSandboxPackageName(), 0, -1) 127 .get(0); 128 assertThat(info).isNotNull(); 129 assertThat(info.getRealUid()).isEqualTo(Process.toSdkSandboxUid(Process.myUid())); 130 assertThat(info.getReason()).isEqualTo(ApplicationExitInfo.REASON_CRASH); 131 assertThat(info.getTimestamp()).isGreaterThan(timeStampBeforeCrash); 132 } 133 134 // Should only be tested after another app has started and crashed their own sandbox. 135 @Test testSdkCannotAccessExitReasonsFromOtherSdkSandboxUids()136 public void testSdkCannotAccessExitReasonsFromOtherSdkSandboxUids() throws Exception { 137 mRule.getScenario(); 138 139 final FakeLoadSdkCallback callback = new FakeLoadSdkCallback(); 140 mSdkSandboxManager.loadSdk(SDK_PACKAGE, new Bundle(), Runnable::run, callback); 141 callback.assertLoadSdkIsSuccessful(); 142 143 final ICrashTestSdkApi sdk = 144 ICrashTestSdkApi.Stub.asInterface(callback.getSandboxedSdk().getInterface()); 145 final List<ApplicationExitInfo> exitInfos = sdk.getSdkSandboxExitReasons(); 146 147 // Test that all exit infos (if any) are only for its own process 148 for (int i = 0; i < exitInfos.size(); i++) { 149 final ApplicationExitInfo exitInfo = exitInfos.get(i); 150 assertThat(exitInfo.getRealUid()).isEqualTo(Process.toSdkSandboxUid(Process.myUid())); 151 } 152 } 153 154 @Test testCrashSandboxGeneratesDropboxReport()155 public void testCrashSandboxGeneratesDropboxReport() throws Exception { 156 mRule.getScenario(); 157 final CountDownLatch latch = new CountDownLatch(1); 158 final BroadcastReceiver receiver = 159 new BroadcastReceiver() { 160 @Override 161 public void onReceive(Context context, Intent intent) { 162 if (!intent.getStringExtra(DropBoxManager.EXTRA_TAG) 163 .equals(TAG_SYSTEM_APP_CRASH)) { 164 return; 165 } 166 final long timeBeforeEntry = 167 intent.getLongExtra(DropBoxManager.EXTRA_TIME, 0) - 1; 168 DropBoxManager.Entry entry = 169 mDropboxManager.getNextEntry(TAG_SYSTEM_APP_CRASH, timeBeforeEntry); 170 if (entry == null) { 171 return; 172 } 173 String entryText = entry.getText(N_DROPBOX_HEADER_BYTES); 174 if (entryText == null) { 175 entry.close(); 176 return; 177 } 178 // Check if SDK sandbox crash 179 if (entryText.contains( 180 String.format( 181 "Package: %s", 182 mPackageManager.getSdkSandboxPackageName()))) { 183 mCrashEntryText = entryText; 184 latch.countDown(); 185 } 186 entry.close(); 187 } 188 }; 189 190 mContext.registerReceiver( 191 receiver, new IntentFilter(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED)); 192 193 generateSdkSandboxCrash(); 194 195 assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); 196 assertThat(mCrashEntryText) 197 .contains( 198 String.format( 199 "SdkSandbox-Client-Package: %s v%d (%s)", 200 PACKAGE_NAME, VERSION_CODE, VERSION_NAME)); 201 assertThat(mCrashEntryText) 202 .contains( 203 String.format( 204 "SdkSandbox-Library: %s v%d", SDK_PACKAGE_NAME, SDK_VERSION_CODE)); 205 } 206 generateSdkSandboxCrash()207 private void generateSdkSandboxCrash() throws Exception { 208 final CountDownLatch deathLatch = new CountDownLatch(1); 209 // Avoid being killed when sandbox crashes 210 mSdkSandboxManager.addSdkSandboxProcessDeathCallback(Runnable::run, deathLatch::countDown); 211 212 final FakeLoadSdkCallback callback = new FakeLoadSdkCallback(); 213 214 // Start and crash the SDK sandbox 215 mSdkSandboxManager.loadSdk(SDK_PACKAGE, new Bundle(), Runnable::run, callback); 216 callback.assertLoadSdkIsSuccessful(); 217 218 final ICrashTestSdkApi sdk = 219 ICrashTestSdkApi.Stub.asInterface(callback.getSandboxedSdk().getInterface()); 220 sdk.triggerCrash(); 221 assertThat(deathLatch.await(5, TimeUnit.SECONDS)).isTrue(); 222 } 223 } 224