1 /*
2  * Copyright (C) 2022 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 /**
18  * Tests for {@link android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager}
19  *
20  * atest CtsWallpaperEffectsGenerationServiceTestCases
21  */
22 package android.wallpapereffectsgeneration.cts;
23 
24 import static androidx.test.InstrumentationRegistry.getContext;
25 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
26 import static com.google.common.truth.Truth.assertWithMessage;
27 import static org.junit.Assert.assertNotNull;
28 import static org.mockito.ArgumentMatchers.any;
29 import static org.mockito.ArgumentMatchers.eq;
30 import static org.mockito.Mockito.reset;
31 import static org.mockito.Mockito.timeout;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.internal.verification.VerificationModeFactory.atMost;
34 
35 import android.app.wallpapereffectsgeneration.CinematicEffectRequest;
36 import android.app.wallpapereffectsgeneration.CinematicEffectResponse;
37 import android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager;
38 import android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager.CinematicEffectListener;
39 import android.content.Context;
40 import android.graphics.Bitmap;
41 import android.os.Process;
42 import android.util.Log;
43 
44 import androidx.annotation.NonNull;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import com.android.compatibility.common.util.RequiredServiceRule;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.Mockito;
55 
56 import java.util.concurrent.CountDownLatch;
57 import java.util.concurrent.Executors;
58 import java.util.concurrent.TimeUnit;
59 
60 /**
61  * Tests for {@link WallpaperEffectsGenerationManager}
62  *
63  * atest CtsWallpaperEffectsGenerationServiceTestCases
64  */
65 
66 @RunWith(AndroidJUnit4.class)
67 public class WallpaperEffectsGenerationManagerTest {
68     private static final String TAG = "WallpaperEffectsGenerationTest";
69     private static final boolean DEBUG = false;
70     private static final long VERIFY_TIMEOUT_MS = 5_000;
71     private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 20_000;
72 
73     @Rule
74     public final RequiredServiceRule mRequiredServiceRule =
75             new RequiredServiceRule(Context.WALLPAPER_EFFECTS_GENERATION_SERVICE);
76 
77     private WallpaperEffectsGenerationManager mManager;
78     private CtsWallpaperEffectsGenerationService.Watcher mWatcher;
79     private CinematicEffectRequest mInitialTaskRequest =
80             createCinematicEffectRequest("initial-task");
81 
82     @Before
setup()83     public void setup() throws Exception {
84         mWatcher = CtsWallpaperEffectsGenerationService.setWatcher();
85         mManager = getContext().getSystemService(WallpaperEffectsGenerationManager.class);
86         setService(CtsWallpaperEffectsGenerationService.SERVICE_NAME);
87         // The wallpaper effects generation services are created lazily,
88         // call one method to start the service for these tests.
89         mWatcher.verifier = Mockito.mock(CtsWallpaperEffectsGenerationService.class);
90         reset(mWatcher.verifier);
91         CtsCinematicEffectListener ctsCinematicEffectListener = new CtsCinematicEffectListener();
92         mManager.generateCinematicEffect(mInitialTaskRequest,
93                 Executors.newSingleThreadExecutor(),
94                 ctsCinematicEffectListener);
95         await(mWatcher.created, "Waiting for onCreated()");
96         // Check the request the server received is the request sent.
97         await(ctsCinematicEffectListener.mOkResponse, "wait for initial task returned");
98         verifyService().onGenerateCinematicEffect(eq(mInitialTaskRequest));
99     }
100 
101     @After
tearDown()102     public void tearDown() throws Exception {
103         setService(null);
104         await(mWatcher.destroyed, "Waiting for onDestroyed()");
105         mWatcher = null;
106         CtsWallpaperEffectsGenerationService.clearWatcher();
107     }
108 
109     @Test
testGenerateCinematicEffect_okResponse()110     public void testGenerateCinematicEffect_okResponse() {
111         mWatcher.verifier = Mockito.mock(CtsWallpaperEffectsGenerationService.class);
112         reset(mWatcher.verifier);
113         assertNotNull(mManager);
114         CinematicEffectRequest request = createSimpleCinematicEffectRequest("ok-task");
115         CtsCinematicEffectListener ctsCinematicEffectListener = new CtsCinematicEffectListener();
116         mManager.generateCinematicEffect(request, Executors.newSingleThreadExecutor(),
117                 ctsCinematicEffectListener);
118         await(ctsCinematicEffectListener.mOkResponse, "Result is okay");
119         verifyService().onGenerateCinematicEffect(eq(request));
120     }
121 
122     @Test
testGenerateCinematicEffect_errorResponse()123     public void testGenerateCinematicEffect_errorResponse() {
124         mWatcher.verifier = Mockito.mock(CtsWallpaperEffectsGenerationService.class);
125         reset(mWatcher.verifier);
126         assertNotNull(mManager);
127         CinematicEffectRequest request = createSimpleCinematicEffectRequest("error-task");
128         CtsCinematicEffectListener ctsCinematicEffectListener = new CtsCinematicEffectListener();
129         mManager.generateCinematicEffect(request, Executors.newSingleThreadExecutor(),
130                 ctsCinematicEffectListener);
131         await(ctsCinematicEffectListener.mErrorResponse, "Result is error");
132         verifyService().onGenerateCinematicEffect(eq(request));
133     }
134 
135     @Test
testGenerateCinematicEffect_pendingResponse()136     public void testGenerateCinematicEffect_pendingResponse() {
137         mWatcher.verifier = Mockito.mock(CtsWallpaperEffectsGenerationService.class);
138         reset(mWatcher.verifier);
139         assertNotNull(mManager);
140         CinematicEffectRequest request1 = createCinematicEffectRequest("pending-task-id");
141         CinematicEffectRequest request2 = createCinematicEffectRequest("pending-task-id");
142         CtsCinematicEffectListener ctsCinematicEffectListener1 = new CtsCinematicEffectListener();
143         CtsCinematicEffectListener ctsCinematicEffectListener2 = new CtsCinematicEffectListener();
144         mManager.generateCinematicEffect(request1, Executors.newSingleThreadExecutor(),
145                 ctsCinematicEffectListener1);
146         mManager.generateCinematicEffect(request2, Executors.newSingleThreadExecutor(),
147                 ctsCinematicEffectListener2);
148         await(ctsCinematicEffectListener2.mPendingResponse,
149                 "2nd result returned and listener invoked");
150         await(ctsCinematicEffectListener1.mOkResponse,
151                 "1st request returned after long processing");
152         verifyService().onGenerateCinematicEffect(eq(request1));
153         verify(mWatcher.verifier, atMost(1)).onGenerateCinematicEffect(any());
154     }
155 
156     @Test
testGenerateCinematicEffect_tooManyRequestsResponse()157     public void testGenerateCinematicEffect_tooManyRequestsResponse() {
158         mWatcher.verifier = Mockito.mock(CtsWallpaperEffectsGenerationService.class);
159         reset(mWatcher.verifier);
160         assertNotNull(mManager);
161         CinematicEffectRequest request1 = createCinematicEffectRequest("pending-task-id");
162         CinematicEffectRequest request2 = createCinematicEffectRequest("other-task-id");
163         CtsCinematicEffectListener ctsCinematicEffectListener1 = new CtsCinematicEffectListener();
164         CtsCinematicEffectListener ctsCinematicEffectListener2 = new CtsCinematicEffectListener();
165         mManager.generateCinematicEffect(request1, Executors.newSingleThreadExecutor(),
166                 ctsCinematicEffectListener1);
167         mManager.generateCinematicEffect(request2, Executors.newSingleThreadExecutor(),
168                 ctsCinematicEffectListener2);
169         await(ctsCinematicEffectListener2.mTooManyRequestsResponse,
170                 "Second request immediately fail with too many requests response");
171         await(ctsCinematicEffectListener1.mOkResponse,
172                 "1st request returned after long processing");
173         verifyService().onGenerateCinematicEffect(eq(request1));
174         verify(mWatcher.verifier, atMost(1)).onGenerateCinematicEffect(any());
175     }
176 
177     private static final class CtsCinematicEffectListener implements CinematicEffectListener {
178         CountDownLatch mOkResponse = new CountDownLatch(1);
179         CountDownLatch mErrorResponse = new CountDownLatch(1);
180         CountDownLatch mPendingResponse = new CountDownLatch(1);
181         CountDownLatch mTooManyRequestsResponse = new CountDownLatch(1);
182 
183         @Override
onCinematicEffectGenerated(CinematicEffectResponse cinematicEffectResponse)184         public void onCinematicEffectGenerated(CinematicEffectResponse cinematicEffectResponse) {
185             Log.d(TAG, "cinematic effect response taskId = " + cinematicEffectResponse.getTaskId()
186                     + ", status code = " + cinematicEffectResponse.getStatusCode());
187             if (cinematicEffectResponse.getStatusCode()
188                     == CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_OK) {
189                 mOkResponse.countDown();
190             } else if (cinematicEffectResponse.getStatusCode()
191                     == CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_PENDING) {
192                 mPendingResponse.countDown();
193             } else if (cinematicEffectResponse.getStatusCode()
194                     == CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS) {
195                 mTooManyRequestsResponse.countDown();
196             } else if (cinematicEffectResponse.getStatusCode()
197                     == CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_ERROR) {
198                 mErrorResponse.countDown();
199             }
200         }
201     }
202 
createCinematicEffectRequest(String taskId)203     private CinematicEffectRequest createCinematicEffectRequest(String taskId) {
204         Bitmap bmp = Bitmap.createBitmap(32, 48, Bitmap.Config.ARGB_8888);
205         return new CinematicEffectRequest(taskId, bmp);
206     }
207 
verifyService()208     private CtsWallpaperEffectsGenerationService verifyService() {
209         return verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS));
210     }
211 
setService(String service)212     private void setService(String service) {
213         if (DEBUG) {
214             Log.d(TAG, "Setting WallpaperEffectsGeneration service to " + service);
215         }
216         int userId = Process.myUserHandle().getIdentifier();
217         String shellCommand = "";
218         if (service != null) {
219             shellCommand = "cmd wallpaper_effects_generation set temporary-service "
220                     + userId + " " + service + " 60000";
221         } else {
222             shellCommand = "cmd wallpaper_effects_generation set temporary-service " + userId;
223         }
224         if (DEBUG) {
225             Log.d(TAG, "runShellCommand(): " + shellCommand);
226         }
227         runShellCommand(shellCommand);
228     }
229 
await(@onNull CountDownLatch latch, @NonNull String message)230     private void await(@NonNull CountDownLatch latch, @NonNull String message) {
231         try {
232             assertWithMessage(message).that(
233                     latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
234         } catch (InterruptedException e) {
235             Thread.currentThread().interrupt();
236             throw new IllegalStateException("Interrupted while: " + message);
237         }
238     }
239 
createSimpleCinematicEffectRequest(String taskId)240     private CinematicEffectRequest createSimpleCinematicEffectRequest(String taskId) {
241         return new CinematicEffectRequest(taskId,
242                 Bitmap.createBitmap(32, 48, Bitmap.Config.ARGB_8888));
243     }
244 }
245