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