1 /* 2 * Copyright (C) 2019 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 package android.contentsuggestions.cts; 17 18 import static androidx.test.InstrumentationRegistry.getContext; 19 import static androidx.test.InstrumentationRegistry.getInstrumentation; 20 21 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 22 import static com.google.common.truth.Truth.assertThat; 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.ArgumentMatchers.eq; 27 import static org.mockito.Mockito.reset; 28 import static org.mockito.Mockito.timeout; 29 import static org.mockito.Mockito.verify; 30 31 import android.app.contentsuggestions.ClassificationsRequest; 32 import android.app.contentsuggestions.ContentSuggestionsManager; 33 import android.app.contentsuggestions.SelectionsRequest; 34 import android.content.Context; 35 import android.graphics.Bitmap; 36 import android.os.Bundle; 37 import android.util.Log; 38 39 import androidx.annotation.NonNull; 40 import androidx.test.runner.AndroidJUnit4; 41 42 import com.android.compatibility.common.util.RequiredServiceRule; 43 44 import com.google.common.collect.Lists; 45 46 import org.junit.After; 47 import org.junit.Before; 48 import org.junit.Rule; 49 import org.junit.Test; 50 import org.junit.runner.RunWith; 51 52 import org.mockito.ArgumentCaptor; 53 54 import java.util.concurrent.CountDownLatch; 55 import java.util.concurrent.Executors; 56 import java.util.concurrent.TimeUnit; 57 58 /** 59 * Tests for {@link ContentSuggestionsManager}. 60 */ 61 @RunWith(AndroidJUnit4.class) 62 public class ContentSuggestionsManagerTest { 63 private static final String TAG = ContentSuggestionsManagerTest.class.getSimpleName(); 64 65 private static final long VERIFY_TIMEOUT_MS = 5_000; 66 private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 30_000; 67 68 @Rule 69 public final RequiredServiceRule mRequiredServiceRule = 70 new RequiredServiceRule(Context.CONTENT_SUGGESTIONS_SERVICE); 71 72 private ContentSuggestionsManager mManager; 73 private CtsContentSuggestionsService.Watcher mWatcher; 74 75 @Before setup()76 public void setup() { 77 mWatcher = CtsContentSuggestionsService.setWatcher(); 78 79 Log.d(TAG, "Test setting service"); 80 mManager = (ContentSuggestionsManager) getContext() 81 .getSystemService(Context.CONTENT_SUGGESTIONS_SERVICE); 82 setService(CtsContentSuggestionsService.SERVICE_COMPONENT.flattenToString()); 83 84 // TODO: b/126587631 remove when the manager calls no longer need this. 85 getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( 86 "android.permission.READ_FRAME_BUFFER"); 87 88 // The ContentSuggestions services are created lazily, have to call one method on it to 89 // start the service for the tests. 90 mManager.notifyInteraction("SETUP", new Bundle()); 91 92 await(mWatcher.created, "Waiting for create"); 93 reset(mWatcher.verifier); 94 Log.d(TAG, "Service set and watcher reset."); 95 } 96 97 @After tearDown()98 public void tearDown() { 99 Log.d(TAG, "Starting tear down, watcher is: " + mWatcher); 100 resetService(); 101 await(mWatcher.destroyed, "Waiting for service destroyed"); 102 103 mWatcher = null; 104 CtsContentSuggestionsService.clearWatcher(); 105 106 // TODO: b/126587631 remove when the manager calls no longer need this. 107 getInstrumentation().getUiAutomation().dropShellPermissionIdentity(); 108 } 109 110 @Test managerForwards_notifyInteraction()111 public void managerForwards_notifyInteraction() { 112 String requestId = "TEST"; 113 114 mManager.notifyInteraction(requestId, new Bundle()); 115 verifyService().onNotifyInteraction(eq(requestId), any()); 116 } 117 118 @Test managerForwards_provideContextImage()119 public void managerForwards_provideContextImage() { 120 int taskId = 1; 121 122 mManager.provideContextImage(taskId, new Bundle()); 123 verifyService().onProcessContextImage(eq(taskId), any(), any()); 124 } 125 126 @Test managerForwards_provideContextBitmap()127 public void managerForwards_provideContextBitmap() { 128 int taskId = -1; // Explicit bitmap is provided; so task id is absent. 129 130 Bitmap expectedBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); 131 mManager.provideContextImage(expectedBitmap, new Bundle()); 132 ArgumentCaptor<Bundle> bundleArg = ArgumentCaptor.forClass(Bundle.class); 133 ArgumentCaptor<Bitmap> bitmapArg = ArgumentCaptor.forClass(Bitmap.class); 134 verifyService().onProcessContextImage(eq(taskId), bitmapArg.capture(), 135 bundleArg.capture()); 136 Bitmap actualBitmap = bundleArg.getValue().getParcelable( 137 ContentSuggestionsManager.EXTRA_BITMAP); 138 139 // Both the Bundle bitmap and the explicit bitmap should match the provided one. 140 assertThat(actualBitmap.getWidth()).isEqualTo(expectedBitmap.getWidth()); 141 assertThat(actualBitmap.getHeight()).isEqualTo(expectedBitmap.getHeight()); 142 assertThat(bitmapArg.getValue().getWidth()).isEqualTo(expectedBitmap.getWidth()); 143 assertThat(bitmapArg.getValue().getHeight()).isEqualTo(expectedBitmap.getHeight()); 144 } 145 146 @Test managerForwards_suggestContentSelections()147 public void managerForwards_suggestContentSelections() { 148 SelectionsRequest request = new SelectionsRequest.Builder(1).build(); 149 ContentSuggestionsManager.SelectionsCallback callback = (statusCode, selections) -> {}; 150 151 152 mManager.suggestContentSelections(request, Executors.newSingleThreadExecutor(), callback); 153 verifyService().onSuggestContentSelections(any(), any()); 154 } 155 156 @Test managerForwards_classifyContentSelections()157 public void managerForwards_classifyContentSelections() { 158 ClassificationsRequest request = new ClassificationsRequest.Builder( 159 Lists.newArrayList()).build(); 160 ContentSuggestionsManager.ClassificationsCallback callback = 161 (statusCode, classifications) -> {}; 162 163 164 mManager.classifyContentSelections(request, Executors.newSingleThreadExecutor(), callback); 165 verifyService().onClassifyContentSelections(any(), any()); 166 } 167 verifyService()168 private CtsContentSuggestionsService verifyService() { 169 return verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS)); 170 } 171 await(@onNull CountDownLatch latch, @NonNull String message)172 private void await(@NonNull CountDownLatch latch, @NonNull String message) { 173 try { 174 assertWithMessage(message).that( 175 latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue(); 176 } catch (InterruptedException e) { 177 Thread.currentThread().interrupt(); 178 throw new IllegalStateException("Interrupted while: " + message); 179 } 180 } 181 182 /** 183 * Sets the content capture service. 184 */ setService(@onNull String service)185 private static void setService(@NonNull String service) { 186 Log.d(TAG, "Setting service to " + service); 187 int userId = android.os.Process.myUserHandle().getIdentifier(); 188 runShellCommand( 189 "cmd content_suggestions set temporary-service %d %s 120000", userId, service); 190 } 191 resetService()192 private static void resetService() { 193 Log.d(TAG, "Resetting service"); 194 int userId = android.os.Process.myUserHandle().getIdentifier(); 195 runShellCommand("cmd content_suggestions set temporary-service %d", userId); 196 } 197 } 198