/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.screenshot; import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Notification; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.Collections; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidTestingRunner.class) /** * Tests for exception handling and bitmap configuration in adding smart actions to Screenshot * Notification. */ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { private ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; private ScreenshotSmartActions mScreenshotSmartActions; private Handler mHandler; @Before public void setup() { mSmartActionsProvider = mock( ScreenshotNotificationSmartActionsProvider.class); mScreenshotSmartActions = new ScreenshotSmartActions(() -> mSmartActionsProvider); mHandler = mock(Handler.class); } // Tests any exception thrown in getting smart actions future does not affect regular // screenshot flow. @Test public void testExceptionHandlingInGetSmartActionsFuture() throws Exception { Bitmap bitmap = mock(Bitmap.class); when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE); ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock( ScreenshotNotificationSmartActionsProvider.class); when(smartActionsProvider.getActions(any(), any(), any(), any(), any(), any())) .thenThrow(RuntimeException.class); CompletableFuture> smartActionsFuture = mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://authority/data"), bitmap, smartActionsProvider, REGULAR_SMART_ACTIONS, true, UserHandle.of(UserHandle.myUserId())); assertNotNull(smartActionsFuture); List smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); assertEquals(Collections.emptyList(), smartActions); } // Tests any exception thrown in waiting for smart actions future to complete does // not affect regular screenshot flow. @Test public void testExceptionHandlingInGetSmartActions() throws Exception { CompletableFuture> smartActionsFuture = mock( CompletableFuture.class); int timeoutMs = 1000; when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow( RuntimeException.class); List actions = mScreenshotSmartActions.getSmartActions( "", smartActionsFuture, timeoutMs, mSmartActionsProvider, REGULAR_SMART_ACTIONS); assertEquals(Collections.emptyList(), actions); } // Tests any exception thrown in notifying feedback does not affect regular screenshot flow. @Test public void testExceptionHandlingInNotifyingFeedback() throws Exception { doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(), anyLong()); mScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1); } // Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked // and a completed future is returned. @Test public void testUnsupportedBitmapConfiguration() throws Exception { Bitmap bitmap = mock(Bitmap.class); when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565); CompletableFuture> smartActionsFuture = mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, REGULAR_SMART_ACTIONS, true, UserHandle.of(UserHandle.myUserId())); verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any(), any()); assertNotNull(smartActionsFuture); List smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); assertEquals(Collections.emptyList(), smartActions); } // Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once. @Test public void testScreenshotNotificationSmartActionsProviderInvokedOnce() { Bitmap bitmap = mock(Bitmap.class); when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE); mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, REGULAR_SMART_ACTIONS, true, UserHandle.of(UserHandle.myUserId())); verify(mSmartActionsProvider, times(1)).getActions( any(), any(), any(), any(), any(), any()); } // Tests for a hardware bitmap, a completed future is returned. @Test public void testSupportedBitmapConfiguration() throws Exception { Bitmap bitmap = mock(Bitmap.class); when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE); ScreenshotNotificationSmartActionsProvider actionsProvider = new ScreenshotNotificationSmartActionsProvider(); CompletableFuture> smartActionsFuture = mScreenshotSmartActions.getSmartActionsFuture("", null, bitmap, actionsProvider, REGULAR_SMART_ACTIONS, true, UserHandle.of(UserHandle.myUserId())); assertNotNull(smartActionsFuture); List smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); assertEquals(smartActions.size(), 0); } // Tests for share action extras @Test public void testShareActionExtras() { if (Looper.myLooper() == null) { Looper.prepare(); } ScreenshotController.SaveImageInBackgroundData data = new ScreenshotController.SaveImageInBackgroundData(); data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, ActionTransition::new, mSmartActionsProvider); Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png"), true).get().action; Intent intent = shareAction.actionIntent.getIntent(); assertNotNull(intent); Bundle bundle = intent.getExtras(); assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID)); assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED)); assertEquals(ScreenshotController.ACTION_TYPE_SHARE, shareAction.title); assertEquals(Intent.ACTION_SEND, intent.getAction()); } // Tests for edit action extras @Test public void testEditActionExtras() { if (Looper.myLooper() == null) { Looper.prepare(); } ScreenshotController.SaveImageInBackgroundData data = new ScreenshotController.SaveImageInBackgroundData(); data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, ActionTransition::new, mSmartActionsProvider); Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png"), true).get().action; Intent intent = editAction.actionIntent.getIntent(); assertNotNull(intent); Bundle bundle = intent.getExtras(); assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID)); assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED)); assertEquals(ScreenshotController.ACTION_TYPE_EDIT, editAction.title); assertEquals(Intent.ACTION_EDIT, intent.getAction()); } // Tests for share action extras @Test public void testDeleteActionExtras() { if (Looper.myLooper() == null) { Looper.prepare(); } ScreenshotController.SaveImageInBackgroundData data = new ScreenshotController.SaveImageInBackgroundData(); data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data, ActionTransition::new, mSmartActionsProvider); Notification.Action deleteAction = task.createDeleteAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png"), true); Intent intent = deleteAction.actionIntent.getIntent(); assertNotNull(intent); Bundle bundle = intent.getExtras(); assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID)); assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED)); assertEquals(deleteAction.title, ScreenshotController.ACTION_TYPE_DELETE); assertNull(intent.getAction()); } }