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