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