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