1 /*
2  * Copyright (C) 2021 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 android.car.cluster.renderer;
18 
19 
20 import static android.app.ActivityOptions.KEY_PACKAGE_NAME;
21 import static android.car.Car.CAR_EXTRA_CLUSTER_ACTIVITY_STATE;
22 import static android.car.Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER;
23 import static android.car.cluster.renderer.InstrumentClusterRenderingService.EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER;
24 import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
25 import static android.content.pm.PackageManager.PERMISSION_DENIED;
26 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
27 import static android.view.KeyEvent.KEYCODE_1;
28 
29 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
30 
31 import static com.google.common.truth.Truth.assertThat;
32 
33 import static org.junit.Assert.assertThrows;
34 import static org.mockito.ArgumentMatchers.any;
35 import static org.mockito.ArgumentMatchers.eq;
36 import static org.mockito.Mockito.spy;
37 import static org.mockito.Mockito.when;
38 
39 import android.app.ActivityManager;
40 import android.app.ActivityOptions;
41 import android.car.CarLibLog;
42 import android.car.cluster.ClusterActivityState;
43 import android.car.test.mocks.AbstractExtendedMockitoTestCase;
44 import android.content.ActivityNotFoundException;
45 import android.content.ComponentName;
46 import android.content.ContentResolver;
47 import android.content.Intent;
48 import android.content.pm.ActivityInfo;
49 import android.content.pm.PackageInfo;
50 import android.content.pm.PackageManager;
51 import android.content.pm.ProviderInfo;
52 import android.content.pm.ResolveInfo;
53 import android.graphics.Bitmap;
54 import android.graphics.BitmapFactory;
55 import android.graphics.Rect;
56 import android.net.Uri;
57 import android.os.Binder;
58 import android.os.Bundle;
59 import android.os.IBinder;
60 import android.os.ParcelFileDescriptor;
61 import android.os.RemoteException;
62 import android.os.UserHandle;
63 import android.view.KeyEvent;
64 
65 import androidx.annotation.NonNull;
66 import androidx.annotation.Nullable;
67 import androidx.test.platform.app.InstrumentationRegistry;
68 import androidx.test.rule.ServiceTestRule;
69 
70 import com.google.common.collect.ImmutableList;
71 
72 import org.junit.Before;
73 import org.junit.Rule;
74 import org.junit.Test;
75 import org.junit.runner.RunWith;
76 import org.mockito.Mock;
77 import org.mockito.junit.MockitoJUnitRunner;
78 
79 import java.io.FileDescriptor;
80 import java.util.List;
81 
82 
83 /** Unit tests for {@link InstrumentClusterRenderingService}. */
84 @RunWith(MockitoJUnitRunner.class)
85 public final class InstrumentClusterRenderingServiceTest extends AbstractExtendedMockitoTestCase {
86     @Mock
87     private NavigationRenderer mNavigationRenderer;
88 
89     @Mock
90     private ParcelFileDescriptor mParcelFileDescriptor;
91 
92     @Mock
93     private FileDescriptor mMockFileDescriptor;
94 
95     @Rule
96     public final ServiceTestRule mServiceRule = new ServiceTestRule();
97 
98     private TestableInstrumentClusterRenderingService mService;
99     private TestableInstrumentClusterHelper mTestableInstrumentClusterHelper;
100     private IInstrumentCluster mRendererBinder;
101 
InstrumentClusterRenderingServiceTest()102     public InstrumentClusterRenderingServiceTest() {
103         super(CarLibLog.TAG_CLUSTER);
104     }
105 
106     @Override
onSessionBuilder(CustomMockitoSessionBuilder builder)107     protected void onSessionBuilder(CustomMockitoSessionBuilder builder) {
108         builder.spyStatic(BitmapFactory.class);
109     }
110 
111     @Before
setup()112     public void setup() {
113         TestableInstrumentClusterRenderingService.setNavigationRenderer(mNavigationRenderer);
114         mTestableInstrumentClusterHelper = new TestableInstrumentClusterHelper();
115     }
116 
bindService(Intent intent)117     private void bindService(Intent intent) throws Exception {
118         intent.setComponent(ComponentName.createRelative(InstrumentationRegistry
119                         .getInstrumentation().getContext(),
120                 TestableInstrumentClusterRenderingService.class.getName()));
121 
122         IBinder binder = mServiceRule.bindService(intent);
123         mService = ((TestableInstrumentClusterRenderingService.BinderWrapper) binder).getService();
124         mRendererBinder =
125                 (IInstrumentCluster) ((TestableInstrumentClusterRenderingService.BinderWrapper)
126                         binder).getParentBinder();
127     }
128 
createBindIntentWithClusterHelper()129     private Intent createBindIntentWithClusterHelper() {
130         Bundle args = new Bundle();
131         args.putBinder(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER,
132                 mTestableInstrumentClusterHelper.asBinder());
133         return new Intent().putExtra(EXTRA_BUNDLE_KEY_FOR_INSTRUMENT_CLUSTER_HELPER, args);
134     }
135 
136     @Test
bindService_success()137     public void bindService_success() throws Exception {
138         bindService(createBindIntentWithClusterHelper());
139 
140         assertThat(mRendererBinder).isNotNull();
141     }
142 
143     @Test
144     @ExpectWtf
bindService_withoutClusterHelper()145     public void bindService_withoutClusterHelper() throws Exception {
146         bindService(new Intent().putExtras(new Bundle()));
147 
148         assertThat(mRendererBinder).isNotNull();
149     }
150 
createActivityResolveInfo(String packageName)151     private List<ResolveInfo> createActivityResolveInfo(String packageName) {
152         ResolveInfo resolveInfo = new ResolveInfo();
153         resolveInfo.activityInfo = new ActivityInfo();
154         resolveInfo.activityInfo.packageName = packageName;
155         resolveInfo.activityInfo.name = "Test";
156         return ImmutableList.of(resolveInfo);
157     }
158 
159     @Test
setNavigationContextOwner_launchesNavigationComponent()160     public void setNavigationContextOwner_launchesNavigationComponent() throws Exception {
161         int userId = ActivityManager.getCurrentUser();
162         String packageName = "com.test";
163         bindService(createBindIntentWithClusterHelper());
164         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
165         ClusterActivityState clusterActivityState = ClusterActivityState
166                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
167         mService.setClusterActivityState(clusterActivityState);
168 
169         doReturn(new String[]{packageName})
170                 .when(mService.mSpyPackageManager).getPackagesForUid(userId);
171         doReturn(PERMISSION_GRANTED)
172                 .when(mService.mSpyPackageManager).checkPermission(
173                 PERMISSION_CAR_DISPLAY_IN_CLUSTER, packageName);
174         doReturn(createActivityResolveInfo(packageName)).when(mService.mSpyPackageManager)
175                 .queryIntentActivitiesAsUser(any(), eq(GET_RESOLVED_FILTER),
176                         eq(UserHandle.of(userId)));
177 
178         mRendererBinder.setNavigationContextOwner(userId, 123);
179 
180         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
181         assertThat(mService.mNumOfTimesOnNavigationComponentLaunchedCalled).isEqualTo(1);
182     }
183 
184     @Test
setNavigationContextOwner_navigationComponentAlreadyLaunched_doesNothing()185     public void setNavigationContextOwner_navigationComponentAlreadyLaunched_doesNothing()
186             throws Exception {
187         int userId = ActivityManager.getCurrentUser();
188         bindService(createBindIntentWithClusterHelper());
189         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
190         ClusterActivityState clusterActivityState = ClusterActivityState
191                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
192         mService.setClusterActivityState(clusterActivityState);
193         mockPackageManagerInteraction(userId);
194 
195         mRendererBinder.setNavigationContextOwner(userId, 123);
196         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
197         mRendererBinder.setNavigationContextOwner(userId, 123);
198         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
199 
200         assertThat(mService.mNumOfTimesOnNavigationComponentLaunchedCalled).isEqualTo(1);
201     }
202 
203     @Test
setNavigationContextOwner_noPackages_doesNothing()204     public void setNavigationContextOwner_noPackages_doesNothing() throws Exception {
205         int userId = ActivityManager.getCurrentUser();
206         bindService(createBindIntentWithClusterHelper());
207         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
208         ClusterActivityState clusterActivityState = ClusterActivityState
209                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
210         mService.setClusterActivityState(clusterActivityState);
211         doReturn(new String[]{}).when(mService.mSpyPackageManager).getPackagesForUid(userId);
212 
213         mRendererBinder.setNavigationContextOwner(userId, 123);
214         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
215 
216         assertThat(mService.mNumOfTimesOnNavigationComponentLaunchedCalled).isEqualTo(0);
217     }
218 
219     @Test
setNavigationContextOwner_clusterPermissionMissing_doesNothing()220     public void setNavigationContextOwner_clusterPermissionMissing_doesNothing() throws Exception {
221         int userId = ActivityManager.getCurrentUser();
222         String packageName = "com.test";
223         bindService(createBindIntentWithClusterHelper());
224         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
225         ClusterActivityState clusterActivityState = ClusterActivityState
226                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
227         mService.setClusterActivityState(clusterActivityState);
228         doReturn(new String[]{packageName})
229                 .when(mService.mSpyPackageManager).getPackagesForUid(userId);
230         doReturn(PERMISSION_DENIED)
231                 .when(mService.mSpyPackageManager).checkPermission(
232                 PERMISSION_CAR_DISPLAY_IN_CLUSTER, packageName);
233 
234         mRendererBinder.setNavigationContextOwner(userId, 123);
235         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
236 
237         assertThat(mService.mNumOfTimesOnNavigationComponentLaunchedCalled).isEqualTo(0);
238     }
239 
240     @Test
setNavigationContextOwner_failureWhenStartingNavigationActivity()241     public void setNavigationContextOwner_failureWhenStartingNavigationActivity()
242             throws Exception {
243         int userId = ActivityManager.getCurrentUser();
244         bindService(createBindIntentWithClusterHelper());
245         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
246         ClusterActivityState clusterActivityState = ClusterActivityState
247                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
248         mService.setClusterActivityState(clusterActivityState);
249         mTestableInstrumentClusterHelper.mRuntimeFailureOnInteraction = true;
250         mockPackageManagerInteraction(userId);
251 
252         mRendererBinder.setNavigationContextOwner(userId, 123);
253         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
254 
255         assertThat(mService.mNumOfTimesOnNavigationComponentLaunchedCalled).isEqualTo(0);
256     }
257 
258     @Test
setNavigationContextOwner_activityNotFoundWhenStartingNavigationActivity()259     public void setNavigationContextOwner_activityNotFoundWhenStartingNavigationActivity()
260             throws Exception {
261         int userId = ActivityManager.getCurrentUser();
262         bindService(createBindIntentWithClusterHelper());
263         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
264         ClusterActivityState clusterActivityState = ClusterActivityState
265                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
266         mService.setClusterActivityState(clusterActivityState);
267         mTestableInstrumentClusterHelper.mActivityNotFoundFailureOnInteraction = true;
268         mockPackageManagerInteraction(userId);
269 
270         mRendererBinder.setNavigationContextOwner(userId, 123);
271         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
272 
273         assertThat(mService.mNumOfTimesOnNavigationComponentLaunchedCalled).isEqualTo(0);
274     }
275 
276     @Test
updateActivityState_notVisible_releasesNavigationComponent()277     public void updateActivityState_notVisible_releasesNavigationComponent() throws Exception {
278         int userId = ActivityManager.getCurrentUser();
279         bindService(createBindIntentWithClusterHelper());
280         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
281         ClusterActivityState clusterActivityState = ClusterActivityState
282                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
283         mService.setClusterActivityState(clusterActivityState);
284         mockPackageManagerInteraction(userId);
285         mRendererBinder.setNavigationContextOwner(userId, 123);
286         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
287 
288         ClusterActivityState clusterActivityNewState = ClusterActivityState
289                 .create(/* visible= */ false, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
290         mService.setClusterActivityState(clusterActivityNewState);
291 
292         assertThat(mService.mOnNavigationComponentReleasedCalled).isTrue();
293     }
294 
mockPackageManagerInteraction(int userId)295     private void mockPackageManagerInteraction(int userId) {
296         String packageName = "com.test";
297         doReturn(new String[]{packageName})
298                 .when(mService.mSpyPackageManager).getPackagesForUid(userId);
299         doReturn(PERMISSION_GRANTED)
300                 .when(mService.mSpyPackageManager).checkPermission(
301                 PERMISSION_CAR_DISPLAY_IN_CLUSTER, packageName);
302         doReturn(createActivityResolveInfo(packageName)).when(mService.mSpyPackageManager)
303                 .queryIntentActivitiesAsUser(any(), eq(GET_RESOLVED_FILTER),
304                         eq(UserHandle.of(userId)));
305     }
306 
307     @Test
clusterOnKeyEvent_triggersOnKeyEventOnService()308     public void clusterOnKeyEvent_triggersOnKeyEventOnService() throws Exception {
309         bindService(createBindIntentWithClusterHelper());
310 
311         mRendererBinder.onKeyEvent(new KeyEvent(KeyEvent.FLAG_EDITOR_ACTION, KEYCODE_1));
312         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
313 
314         assertThat(mService.mOnKeyEventCalled).isTrue();
315     }
316 
317     @Test
onKeyEvent_doesNothing()318     public void onKeyEvent_doesNothing() throws Exception {
319         bindService(createBindIntentWithClusterHelper());
320 
321         mService.onKeyEvent(new KeyEvent(KeyEvent.FLAG_EDITOR_ACTION, KEYCODE_1));
322 
323         assertThat(mService.mOnKeyEventCalled).isTrue();
324     }
325 
326     @Test
onNavigationComponentLaunched_doesNothing()327     public void onNavigationComponentLaunched_doesNothing() throws Exception {
328         bindService(createBindIntentWithClusterHelper());
329 
330         mService.onNavigationComponentLaunched();
331 
332         assertThat(mService.mNumOfTimesOnNavigationComponentLaunchedCalled).isEqualTo(1);
333     }
334 
335     @Test
onNavigationComponentReleased_doesNothing()336     public void onNavigationComponentReleased_doesNothing() throws Exception {
337         bindService(createBindIntentWithClusterHelper());
338 
339         mService.onNavigationComponentReleased();
340 
341         assertThat(mService.mOnNavigationComponentReleasedCalled).isTrue();
342     }
343 
344     @Test
startFixedActivityMode_callsClusterHelper()345     public void startFixedActivityMode_callsClusterHelper() throws Exception {
346         ClusterActivityState clusterActivityState = ClusterActivityState
347                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
348         Intent intent = new Intent().putExtra(CAR_EXTRA_CLUSTER_ACTIVITY_STATE,
349                 clusterActivityState.toBundle());
350         Bundle activityOptionsBundle = new Bundle();
351         activityOptionsBundle.putString(KEY_PACKAGE_NAME, "temp.pkg");
352         ActivityOptions activityOptions = ActivityOptions.fromBundle(activityOptionsBundle);
353         int userId = ActivityManager.getCurrentUser();
354 
355         bindService(createBindIntentWithClusterHelper());
356         boolean succeeded = mService.startFixedActivityModeForDisplayAndUser(intent,
357                 activityOptions,
358                 userId);
359 
360         assertThat(succeeded).isTrue();
361         assertThat(mTestableInstrumentClusterHelper.mStartFixedActivityModeUserId).isEqualTo(
362                 userId);
363         assertThat(ClusterActivityState.fromBundle(
364                 mTestableInstrumentClusterHelper.mStartFixedActivityIntent
365                         .getBundleExtra(CAR_EXTRA_CLUSTER_ACTIVITY_STATE)).toString())
366                 .isEqualTo(clusterActivityState.toString());
367         assertThat(mTestableInstrumentClusterHelper.mStartFixedActivityModeActivityOptions
368                 .getString(KEY_PACKAGE_NAME))
369                 .isEqualTo("temp.pkg");
370     }
371 
372     @Test
startFixedActivityMode_activityStateMissingInTheIntent()373     public void startFixedActivityMode_activityStateMissingInTheIntent() throws Exception {
374         bindService(createBindIntentWithClusterHelper());
375         ClusterActivityState clusterActivityState = ClusterActivityState
376                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
377         mService.setClusterActivityState(clusterActivityState);
378 
379         Bundle activityOptionsBundle = new Bundle();
380         activityOptionsBundle.putString(KEY_PACKAGE_NAME, "temp.pkg");
381         ActivityOptions activityOptions = ActivityOptions.fromBundle(activityOptionsBundle);
382         int userId = ActivityManager.getCurrentUser();
383 
384         boolean succeeded = mService.startFixedActivityModeForDisplayAndUser(new Intent(),
385                 activityOptions,
386                 userId);
387 
388         assertThat(succeeded).isTrue();
389         assertThat(mTestableInstrumentClusterHelper.mStartFixedActivityModeUserId).isEqualTo(
390                 userId);
391         assertThat(ClusterActivityState.fromBundle(
392                 mTestableInstrumentClusterHelper.mStartFixedActivityIntent
393                         .getBundleExtra(CAR_EXTRA_CLUSTER_ACTIVITY_STATE)).toString())
394                 .isEqualTo(clusterActivityState.toString());
395         assertThat(mTestableInstrumentClusterHelper.mStartFixedActivityModeActivityOptions
396                 .getString(KEY_PACKAGE_NAME))
397                 .isEqualTo("temp.pkg");
398     }
399 
400     @Test
startFixedActivityMode_clusterHelperFailure()401     public void startFixedActivityMode_clusterHelperFailure() throws Exception {
402         mTestableInstrumentClusterHelper.mRemoteFailureOnInteraction = true;
403         bindService(createBindIntentWithClusterHelper());
404         ClusterActivityState clusterActivityState = ClusterActivityState
405                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4));
406         mService.setClusterActivityState(clusterActivityState);
407 
408         Bundle activityOptionsBundle = new Bundle();
409         activityOptionsBundle.putString(KEY_PACKAGE_NAME, "temp.pkg");
410         ActivityOptions activityOptions = ActivityOptions.fromBundle(activityOptionsBundle);
411         int userId = ActivityManager.getCurrentUser();
412 
413         boolean succeeded = mService.startFixedActivityModeForDisplayAndUser(new Intent(),
414                 activityOptions,
415                 userId);
416 
417         assertThat(succeeded).isFalse();
418     }
419 
420     @ExpectWtf
421     @Test
startFixedActivityMode_clusterHelperAbsent()422     public void startFixedActivityMode_clusterHelperAbsent() throws Exception {
423         int userId = ActivityManager.getCurrentUser();
424         bindService(new Intent().putExtras(new Bundle()));
425 
426         mService.onBind(new Intent().putExtras(new Bundle()));
427         boolean succeeded = mService.startFixedActivityModeForDisplayAndUser(new Intent(),
428                 ActivityOptions.fromBundle(new Bundle()),
429                 userId);
430 
431         assertThat(succeeded).isFalse();
432     }
433 
434     @Test
stopFixedActivityMode()435     public void stopFixedActivityMode() throws Exception {
436         int displayId = 1;
437         bindService(createBindIntentWithClusterHelper());
438 
439         mService.stopFixedActivityMode(displayId);
440 
441         assertThat(mTestableInstrumentClusterHelper.mStopFixedActivityModeDisplayId).isEqualTo(
442                 displayId);
443     }
444 
445     @ExpectWtf
446     @Test
stopFixedActivityMode_clusterHelperAbsent()447     public void stopFixedActivityMode_clusterHelperAbsent() throws Exception {
448         int displayId = 1;
449         bindService(new Intent().putExtras(new Bundle()));
450 
451         mService.stopFixedActivityMode(displayId);
452 
453         assertThat(mTestableInstrumentClusterHelper.mStopFixedActivityModeDisplayId).isNotEqualTo(
454                 displayId);
455     }
456 
457     @Test
stopFixedActivityMode_clusterHelperFailure()458     public void stopFixedActivityMode_clusterHelperFailure() throws Exception {
459         int displayId = 1;
460         bindService(createBindIntentWithClusterHelper());
461         mTestableInstrumentClusterHelper.mRemoteFailureOnInteraction = true;
462 
463         mService.stopFixedActivityMode(displayId);
464     }
465 
466     @Test
getBitmap_invalidHeight()467     public void getBitmap_invalidHeight() throws Exception {
468         bindService(createBindIntentWithClusterHelper());
469 
470         assertThrows(IllegalArgumentException.class,
471                 () -> mService.getBitmap(Uri.parse("content://temp.com/tempFile"), 100, -1)
472         );
473     }
474 
475     @Test
getBitmap_invalidOffLanesAlpha()476     public void getBitmap_invalidOffLanesAlpha() throws Exception {
477         bindService(createBindIntentWithClusterHelper());
478 
479         assertThrows(IllegalArgumentException.class,
480                 () -> mService.getBitmap(Uri.parse("content://temp.com/tempFile"), 100, 100, 1.1f)
481         );
482     }
483 
484     @Test
getBitmap_contextOwnerMissing()485     public void getBitmap_contextOwnerMissing() throws Exception {
486         bindService(createBindIntentWithClusterHelper());
487 
488         Bitmap bitmap =
489                 mService.getBitmap(Uri.parse("content://temp.com/tempFile"), 100, 100);
490 
491         assertThat(bitmap).isNull();
492     }
493 
494     @Test
getBitmap_unknownAuthority()495     public void getBitmap_unknownAuthority() throws Exception {
496         // Arrange
497         String packageName = "com.test";
498         int userId = ActivityManager.getCurrentUser();
499         bindService(createBindIntentWithClusterHelper());
500         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
501         mService.setClusterActivityState(ClusterActivityState
502                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4)));
503 
504         mockPackageManagerInteraction(userId);
505         mockAuthorityForPackage(null, packageName);
506         mRendererBinder.setNavigationContextOwner(userId, 123);
507         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
508 
509         // Act
510         Bitmap bitmap =
511                 mService.getBitmap(Uri.parse("content://temp.com/tempFile"), 100, 100);
512 
513         // Assert
514         assertThat(bitmap).isNull();
515     }
516 
517     @Test
getBitmap_success()518     public void getBitmap_success() throws Exception {
519         // Arrange
520         String packageName = "com.test";
521         int userId = ActivityManager.getCurrentUser();
522         Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
523 
524         bindService(createBindIntentWithClusterHelper());
525         mService.setClusterActivityLaunchOptions(ActivityOptions.makeBasic());
526         mService.setClusterActivityState(ClusterActivityState
527                 .create(/* visible= */ true, /* unobscuredBounds= */new Rect(1, 2, 3, 4)));
528 
529         mockPackageManagerInteraction(userId);
530         mockAuthorityForPackage("temp.com", packageName);
531         mockBitmapReadingFromFileDescriptor(bitmap);
532         mRendererBinder.setNavigationContextOwner(userId, 123);
533         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
534 
535         // Act
536         Bitmap bitmapReturned =
537                 mService.getBitmap(Uri.parse("content://temp.com/tempFile"), 100, 100);
538 
539         // Assert
540         assertThat(bitmapReturned).isEqualTo(bitmap);
541     }
542 
mockBitmapReadingFromFileDescriptor(Bitmap bitmap)543     private void mockBitmapReadingFromFileDescriptor(Bitmap bitmap) throws Exception {
544         doReturn(bitmap).when(() -> BitmapFactory.decodeFileDescriptor(mMockFileDescriptor));
545         when(mParcelFileDescriptor.getFileDescriptor()).thenReturn(mMockFileDescriptor);
546         doReturn(mParcelFileDescriptor).when(mService.mSpyContentResolver)
547                 .openFileDescriptor(any(), eq("r"));
548     }
549 
mockAuthorityForPackage(@ullable String authority, String packageName)550     private void mockAuthorityForPackage(@Nullable String authority, String packageName)
551             throws Exception {
552         PackageInfo packageInfo = new PackageInfo();
553         if (authority == null) {
554             packageInfo.providers = null;
555         } else {
556             ProviderInfo providerInfo = new ProviderInfo();
557             providerInfo.authority = authority;
558             packageInfo.providers = new ProviderInfo[]{providerInfo};
559         }
560 
561         doReturn(packageInfo).when(mService.mSpyPackageManager)
562                 .getPackageInfo(packageName,
563                         PackageManager.GET_PROVIDERS | PackageManager.MATCH_ANY_USER);
564     }
565 
566     /**
567      * A fake InstrumentClusterHelper stub used for testing. This can't be mocked because this
568      * is sent via binder.
569      */
570     private static final class TestableInstrumentClusterHelper extends
571             IInstrumentClusterHelper.Stub {
572         Intent mStartFixedActivityIntent = null;
573         int mStartFixedActivityModeUserId = -1;
574         Bundle mStartFixedActivityModeActivityOptions = null;
575         boolean mRemoteFailureOnInteraction = false;
576         boolean mRuntimeFailureOnInteraction = false;
577         boolean mActivityNotFoundFailureOnInteraction = false;
578         int mStopFixedActivityModeDisplayId = -1;
579 
580         @Override
startFixedActivityModeForDisplayAndUser(Intent intent, Bundle activityOptionsBundle, int userId)581         public boolean startFixedActivityModeForDisplayAndUser(Intent intent,
582                 Bundle activityOptionsBundle, int userId) throws RemoteException {
583             if (mRemoteFailureOnInteraction) {
584                 throw new RemoteException("failure");
585             }
586             if (mRuntimeFailureOnInteraction) {
587                 throw new RuntimeException("failure");
588             }
589             if (mActivityNotFoundFailureOnInteraction) {
590                 throw new ActivityNotFoundException("failure");
591             }
592             mStartFixedActivityIntent = intent;
593             mStartFixedActivityModeActivityOptions = activityOptionsBundle;
594             mStartFixedActivityModeUserId = userId;
595             return true;
596         }
597 
598         @Override
stopFixedActivityMode(int displayId)599         public void stopFixedActivityMode(int displayId) throws RemoteException {
600             if (mRemoteFailureOnInteraction) {
601                 throw new RemoteException("failure");
602             }
603             if (mRuntimeFailureOnInteraction) {
604                 throw new RuntimeException("failure");
605             }
606             if (mActivityNotFoundFailureOnInteraction) {
607                 throw new ActivityNotFoundException("failure");
608             }
609             mStopFixedActivityModeDisplayId = displayId;
610         }
611     }
612 
613     /**
614      * The {@link InstrumentClusterRenderingService} can't be tested directly because it has no
615      * way to provide the service object.
616      * This class helps is providng the service object using {@link BinderWrapper} and also helps
617      * to spy on the package manager.
618      */
619     public static final class TestableInstrumentClusterRenderingService extends
620             InstrumentClusterRenderingService {
621         private static NavigationRenderer sNavigationRenderer;
622         PackageManager mSpyPackageManager;
623         ContentResolver mSpyContentResolver;
624         boolean mOnKeyEventCalled;
625         int mNumOfTimesOnNavigationComponentLaunchedCalled;
626         boolean mOnNavigationComponentReleasedCalled;
627 
628         private IBinder mParentBinder;
629         private final IBinder mBinder = new BinderWrapper();
630 
setNavigationRenderer(NavigationRenderer navigationRenderer)631         static void setNavigationRenderer(NavigationRenderer navigationRenderer) {
632             sNavigationRenderer = navigationRenderer;
633         }
634 
635         /**
636          * A container class that contains the service object and the binder returned from the
637          * base {@link InstrumentClusterRenderingService}.
638          */
639         public final class BinderWrapper extends Binder {
getService()640             TestableInstrumentClusterRenderingService getService() {
641                 return TestableInstrumentClusterRenderingService.this;
642             }
643 
getParentBinder()644             IBinder getParentBinder() {
645                 return mParentBinder;
646             }
647         }
648 
649         @Override
onBind(Intent intent)650         public IBinder onBind(Intent intent) {
651             mParentBinder = super.onBind(intent);
652             mSpyPackageManager = spy(super.getPackageManager());
653             mSpyContentResolver = spy(super.getContentResolver());
654             return mBinder;
655         }
656 
657         @Nullable
658         @Override
getNavigationRenderer()659         public NavigationRenderer getNavigationRenderer() {
660             return sNavigationRenderer;
661         }
662 
663         @Override
onKeyEvent(@onNull KeyEvent keyEvent)664         public void onKeyEvent(@NonNull KeyEvent keyEvent) {
665             mOnKeyEventCalled = true;
666             super.onKeyEvent(keyEvent);
667         }
668 
669         @Override
onNavigationComponentLaunched()670         public void onNavigationComponentLaunched() {
671             mNumOfTimesOnNavigationComponentLaunchedCalled++;
672             super.onNavigationComponentLaunched();
673         }
674 
675         @Override
onNavigationComponentReleased()676         public void onNavigationComponentReleased() {
677             mOnNavigationComponentReleasedCalled = true;
678             super.onNavigationComponentReleased();
679         }
680 
681         @Override
getPackageManager()682         public PackageManager getPackageManager() {
683             return mSpyPackageManager;
684         }
685 
686         @Override
getContentResolver()687         public ContentResolver getContentResolver() {
688             return mSpyContentResolver;
689         }
690     }
691 }
692