1 /*
2  * Copyright (C) 2023 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.systemui.screenshot.appclips;
18 
19 import static android.app.Activity.RESULT_OK;
20 
21 import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
22 import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
26 
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.ArgumentMatchers.eq;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.content.Intent;
33 import android.content.pm.ApplicationInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.PackageManager.ApplicationInfoFlags;
36 import android.graphics.Bitmap;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.os.Parcel;
40 import android.os.Process;
41 import android.os.ResultReceiver;
42 import android.testing.AndroidTestingRunner;
43 import android.view.Display;
44 import android.widget.ImageView;
45 
46 import androidx.test.rule.ActivityTestRule;
47 import androidx.test.runner.intercepting.SingleActivityFactory;
48 
49 import com.android.internal.logging.UiEventLogger;
50 import com.android.systemui.SysuiTestCase;
51 import com.android.systemui.res.R;
52 import com.android.systemui.screenshot.ImageExporter;
53 import com.android.systemui.settings.UserTracker;
54 
55 import com.google.common.util.concurrent.Futures;
56 
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Ignore;
60 import org.junit.Rule;
61 import org.junit.Test;
62 import org.junit.runner.RunWith;
63 import org.mockito.Mock;
64 import org.mockito.MockitoAnnotations;
65 
66 import java.util.UUID;
67 import java.util.concurrent.Executor;
68 import java.util.function.BiConsumer;
69 
70 @RunWith(AndroidTestingRunner.class)
71 public final class AppClipsActivityTest extends SysuiTestCase {
72 
73     private static final int TEST_UID = 42;
74     private static final int TEST_USER_ID = 43;
75     private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
76     private static final String TEST_URI_STRING = "www.test-uri.com";
77     private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING);
78     private static final BiConsumer<Integer, Bundle> FAKE_CONSUMER = (unUsed1, unUsed2) -> {};
79     private static final String TEST_CALLING_PACKAGE = "test-calling-package";
80 
81     @Mock
82     private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
83     @Mock
84     private ImageExporter mImageExporter;
85     @Mock
86     private PackageManager mPackageManager;
87     @Mock
88     private UserTracker mUserTracker;
89     @Mock
90     private UiEventLogger mUiEventLogger;
91 
92     private AppClipsActivity mActivity;
93 
94     // Using the deprecated ActivityTestRule and SingleActivityFactory to help with injecting mocks.
95     private final SingleActivityFactory<AppClipsActivityTestable> mFactory =
96             new SingleActivityFactory<>(AppClipsActivityTestable.class) {
97                 @Override
98                 protected AppClipsActivityTestable create(Intent unUsed) {
99                     return new AppClipsActivityTestable(
100                             new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper,
101                                     mImageExporter, getContext().getMainExecutor(),
102                                     directExecutor()), mPackageManager, mUserTracker,
103                             mUiEventLogger);
104                 }
105             };
106 
107     @Rule
108     public final ActivityTestRule<AppClipsActivityTestable> mActivityRule =
109             new ActivityTestRule<>(mFactory, false, false);
110 
111     @Before
setUp()112     public void setUp() throws PackageManager.NameNotFoundException {
113         MockitoAnnotations.initMocks(this);
114 
115         when(mUserTracker.getUserId()).thenReturn(TEST_USER_ID);
116         ApplicationInfo applicationInfo = new ApplicationInfo();
117         applicationInfo.uid = TEST_UID;
118         when(mPackageManager.getApplicationInfoAsUser(eq(TEST_CALLING_PACKAGE),
119                 any(ApplicationInfoFlags.class), eq(TEST_USER_ID))).thenReturn(applicationInfo);
120 
121         when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(TEST_BITMAP);
122         ImageExporter.Result result = new ImageExporter.Result();
123         result.uri = TEST_URI;
124         when(mImageExporter.export(any(Executor.class), any(UUID.class), any(Bitmap.class),
125                 eq(Process.myUserHandle()), eq(Display.DEFAULT_DISPLAY)))
126                 .thenReturn(Futures.immediateFuture(result));
127     }
128 
129     @After
tearDown()130     public void tearDown() {
131         mActivityRule.finishActivity();
132     }
133 
134     @Test
appClipsLaunched_screenshotDisplayed()135     public void appClipsLaunched_screenshotDisplayed() {
136         launchActivity();
137 
138         assertThat(((ImageView) mActivity.findViewById(R.id.preview)).getDrawable()).isNotNull();
139     }
140 
141     @Test
142     @Ignore("b/315848285")
screenshotDisplayed_userConsented_screenshotExportedSuccessfully()143     public void screenshotDisplayed_userConsented_screenshotExportedSuccessfully() {
144         ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
145             assertThat(resultCode).isEqualTo(RESULT_OK);
146             assertThat(
147                     data.getParcelable(AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI, Uri.class))
148                     .isEqualTo(TEST_URI);
149             assertThat(data.getInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE))
150                     .isEqualTo(Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
151         });
152 
153         launchActivity(resultReceiver);
154         runOnMainThread(() -> mActivity.findViewById(R.id.save).performClick());
155         waitForIdleSync();
156 
157         assertThat(mActivity.isFinishing()).isTrue();
158         verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_ACCEPTED, TEST_UID, TEST_CALLING_PACKAGE);
159     }
160 
161     @Test
screenshotDisplayed_userDeclined()162     public void screenshotDisplayed_userDeclined() {
163         ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
164             assertThat(resultCode).isEqualTo(RESULT_OK);
165             assertThat(data.getInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE))
166                     .isEqualTo(Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
167             assertThat(data.keySet().contains(AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI))
168                     .isFalse();
169         });
170 
171         launchActivity(resultReceiver);
172         runOnMainThread(() -> mActivity.findViewById(R.id.cancel).performClick());
173         waitForIdleSync();
174 
175         assertThat(mActivity.isFinishing()).isTrue();
176         verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_CANCELLED, TEST_UID, TEST_CALLING_PACKAGE);
177     }
178 
launchActivity()179     private void launchActivity() {
180         launchActivity(createResultReceiver(FAKE_CONSUMER));
181     }
182 
launchActivity(ResultReceiver resultReceiver)183     private void launchActivity(ResultReceiver resultReceiver) {
184         Intent intent = new Intent()
185                 .putExtra(AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER, resultReceiver)
186                 .putExtra(AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME,
187                         TEST_CALLING_PACKAGE);
188 
189         mActivity = mActivityRule.launchActivity(intent);
190         waitForIdleSync();
191     }
192 
createResultReceiver( BiConsumer<Integer, Bundle> resultReceiverConsumer)193     private ResultReceiver createResultReceiver(
194             BiConsumer<Integer, Bundle> resultReceiverConsumer) {
195         ResultReceiver testReceiver = new ResultReceiver(mContext.getMainThreadHandler()) {
196             @Override
197             protected void onReceiveResult(int resultCode, Bundle resultData) {
198                 resultReceiverConsumer.accept(resultCode, resultData);
199             }
200         };
201 
202         Parcel parcel = Parcel.obtain();
203         testReceiver.writeToParcel(parcel, 0);
204         parcel.setDataPosition(0);
205 
206         testReceiver  = ResultReceiver.CREATOR.createFromParcel(parcel);
207         parcel.recycle();
208         return testReceiver;
209     }
210 
runOnMainThread(Runnable runnable)211     private void runOnMainThread(Runnable runnable) {
212         mContext.getMainExecutor().execute(runnable);
213     }
214 
215     public static class AppClipsActivityTestable extends AppClipsActivity {
216 
AppClipsActivityTestable(AppClipsViewModel.Factory viewModelFactory, PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger)217         public AppClipsActivityTestable(AppClipsViewModel.Factory viewModelFactory,
218                 PackageManager packageManager,
219                 UserTracker userTracker,
220                 UiEventLogger uiEventLogger) {
221             super(viewModelFactory, packageManager, userTracker, uiEventLogger);
222         }
223     }
224 }
225