1 /* 2 * Copyright (C) 2018 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 com.android.textclassifier.testing; 18 19 import static org.mockito.ArgumentMatchers.any; 20 import static org.mockito.ArgumentMatchers.anyInt; 21 import static org.mockito.Mockito.mock; 22 import static org.mockito.Mockito.when; 23 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.ContextWrapper; 27 import android.content.Intent; 28 import android.content.pm.ActivityInfo; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ResolveInfo; 32 import androidx.test.core.app.ApplicationProvider; 33 import com.google.common.base.Preconditions; 34 import com.google.errorprone.annotations.CanIgnoreReturnValue; 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.UUID; 38 import javax.annotation.Nullable; 39 import org.mockito.stubbing.Answer; 40 41 /** A builder used to build a fake context for testing. */ 42 public final class FakeContextBuilder { 43 44 /** A component name that can be used for tests. */ 45 public static final ComponentName DEFAULT_COMPONENT = new ComponentName("pkg", "cls"); 46 47 private final PackageManager packageManager; 48 private final ContextWrapper context; 49 private final Map<String, ComponentName> components = new HashMap<>(); 50 private final Map<String, CharSequence> appLabels = new HashMap<>(); 51 @Nullable private ComponentName allIntentComponent; 52 FakeContextBuilder()53 public FakeContextBuilder() { 54 packageManager = mock(PackageManager.class); 55 when(packageManager.resolveActivity(any(Intent.class), anyInt())).thenReturn(null); 56 context = 57 new ContextWrapper(ApplicationProvider.getApplicationContext()) { 58 @Override 59 public PackageManager getPackageManager() { 60 return packageManager; 61 } 62 }; 63 } 64 65 /** 66 * Sets the component name of an activity to handle the specified intent action. 67 * 68 * <p><strong>NOTE: </strong>By default, no component is set to handle any intent. 69 */ 70 @CanIgnoreReturnValue setIntentComponent( String intentAction, @Nullable ComponentName component)71 public FakeContextBuilder setIntentComponent( 72 String intentAction, @Nullable ComponentName component) { 73 Preconditions.checkNotNull(intentAction); 74 components.put(intentAction, component); 75 return this; 76 } 77 78 /** Sets the app label res for a specified package. */ 79 @CanIgnoreReturnValue setAppLabel(String packageName, @Nullable CharSequence appLabel)80 public FakeContextBuilder setAppLabel(String packageName, @Nullable CharSequence appLabel) { 81 Preconditions.checkNotNull(packageName); 82 appLabels.put(packageName, appLabel); 83 return this; 84 } 85 86 /** 87 * Sets the component name of an activity to handle all intents. 88 * 89 * <p><strong>NOTE: </strong>By default, no component is set to handle any intent. 90 */ 91 @CanIgnoreReturnValue setAllIntentComponent(@ullable ComponentName component)92 public FakeContextBuilder setAllIntentComponent(@Nullable ComponentName component) { 93 allIntentComponent = component; 94 return this; 95 } 96 97 /** Builds and returns a fake context. */ build()98 public Context build() { 99 when(packageManager.resolveActivity(any(Intent.class), anyInt())) 100 .thenAnswer( 101 (Answer<ResolveInfo>) 102 invocation -> { 103 final String action = ((Intent) invocation.getArgument(0)).getAction(); 104 final ComponentName component = 105 components.containsKey(action) ? components.get(action) : allIntentComponent; 106 return getResolveInfo(component); 107 }); 108 when(packageManager.getApplicationLabel(any(ApplicationInfo.class))) 109 .thenAnswer( 110 (Answer<CharSequence>) 111 invocation -> { 112 ApplicationInfo applicationInfo = invocation.getArgument(0); 113 return appLabels.get(applicationInfo.packageName); 114 }); 115 return context; 116 } 117 118 /** Returns a component name with random package and class names. */ newComponent()119 public static ComponentName newComponent() { 120 return new ComponentName(UUID.randomUUID().toString(), UUID.randomUUID().toString()); 121 } 122 getResolveInfo(ComponentName component)123 private static ResolveInfo getResolveInfo(ComponentName component) { 124 final ResolveInfo info; 125 if (component == null) { 126 info = null; 127 } else { 128 // NOTE: If something breaks in TextClassifier because we expect more fields to be set 129 // in here, just add them. 130 info = new ResolveInfo(); 131 info.activityInfo = new ActivityInfo(); 132 info.activityInfo.packageName = component.getPackageName(); 133 info.activityInfo.name = component.getClassName(); 134 info.activityInfo.exported = true; 135 info.activityInfo.applicationInfo = new ApplicationInfo(); 136 info.activityInfo.applicationInfo.packageName = component.getPackageName(); 137 info.activityInfo.applicationInfo.icon = 0; 138 } 139 return info; 140 } 141 } 142