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