1 /* 2 * Copyright (C) 2017 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.intentresolver; 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.annotation.Nullable; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.content.pm.ResolveInfo; 29 import android.graphics.drawable.Drawable; 30 import android.os.Bundle; 31 import android.os.UserHandle; 32 import android.util.Pair; 33 34 import androidx.annotation.NonNull; 35 import androidx.test.espresso.idling.CountingIdlingResource; 36 37 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; 38 import com.android.intentresolver.chooser.DisplayResolveInfo; 39 import com.android.intentresolver.chooser.SelectableTargetInfo; 40 import com.android.intentresolver.chooser.TargetInfo; 41 import com.android.intentresolver.icons.TargetDataLoader; 42 43 import java.util.List; 44 import java.util.function.Consumer; 45 import java.util.function.Function; 46 47 /* 48 * Simple wrapper around chooser activity to be able to initiate it under test 49 */ 50 public class ResolverWrapperActivity extends ResolverActivity { 51 static final OverrideData sOverrides = new OverrideData(); 52 53 private final CountingIdlingResource mLabelIdlingResource = 54 new CountingIdlingResource("LoadLabelTask"); 55 ResolverWrapperActivity()56 public ResolverWrapperActivity() { 57 super(/* isIntentPicker= */ true); 58 } 59 60 // ResolverActivity inspects the launched-from UID at onCreate and needs to see some 61 // non-negative value in the test. 62 @Override getLaunchedFromUid()63 public int getLaunchedFromUid() { 64 return 1234; 65 } 66 getLabelIdlingResource()67 public CountingIdlingResource getLabelIdlingResource() { 68 return mLabelIdlingResource; 69 } 70 71 @Override createResolverListAdapter( Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, UserHandle userHandle, TargetDataLoader targetDataLoader)72 public ResolverListAdapter createResolverListAdapter( 73 Context context, 74 List<Intent> payloadIntents, 75 Intent[] initialIntents, 76 List<ResolveInfo> rList, 77 boolean filterLastUsed, 78 UserHandle userHandle, 79 TargetDataLoader targetDataLoader) { 80 return new ResolverListAdapter( 81 context, 82 payloadIntents, 83 initialIntents, 84 rList, 85 filterLastUsed, 86 createListController(userHandle), 87 userHandle, 88 payloadIntents.get(0), // TODO: extract upstream 89 this, 90 userHandle, 91 new TargetDataLoaderWrapper(targetDataLoader, mLabelIdlingResource)); 92 } 93 94 @Override createCrossProfileIntentsChecker()95 protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() { 96 if (sOverrides.mCrossProfileIntentsChecker != null) { 97 return sOverrides.mCrossProfileIntentsChecker; 98 } 99 return super.createCrossProfileIntentsChecker(); 100 } 101 102 @Override createWorkProfileAvailabilityManager()103 protected WorkProfileAvailabilityManager createWorkProfileAvailabilityManager() { 104 if (sOverrides.mWorkProfileAvailability != null) { 105 return sOverrides.mWorkProfileAvailability; 106 } 107 return super.createWorkProfileAvailabilityManager(); 108 } 109 getAdapter()110 ResolverListAdapter getAdapter() { 111 return mMultiProfilePagerAdapter.getActiveListAdapter(); 112 } 113 getPersonalListAdapter()114 ResolverListAdapter getPersonalListAdapter() { 115 return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0)); 116 } 117 getWorkListAdapter()118 ResolverListAdapter getWorkListAdapter() { 119 if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { 120 return null; 121 } 122 return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1)); 123 } 124 125 @Override isVoiceInteraction()126 public boolean isVoiceInteraction() { 127 if (sOverrides.isVoiceInteraction != null) { 128 return sOverrides.isVoiceInteraction; 129 } 130 return super.isVoiceInteraction(); 131 } 132 133 @Override safelyStartActivityInternal(TargetInfo cti, UserHandle user, @Nullable Bundle options)134 public void safelyStartActivityInternal(TargetInfo cti, UserHandle user, 135 @Nullable Bundle options) { 136 if (sOverrides.onSafelyStartInternalCallback != null 137 && sOverrides.onSafelyStartInternalCallback.apply(new Pair<>(cti, user))) { 138 return; 139 } 140 super.safelyStartActivityInternal(cti, user, options); 141 } 142 143 @Override createListController(UserHandle userHandle)144 protected ResolverListController createListController(UserHandle userHandle) { 145 if (userHandle == UserHandle.SYSTEM) { 146 return sOverrides.resolverListController; 147 } 148 return sOverrides.workResolverListController; 149 } 150 151 @Override getPackageManager()152 public PackageManager getPackageManager() { 153 if (sOverrides.createPackageManager != null) { 154 return sOverrides.createPackageManager.apply(super.getPackageManager()); 155 } 156 return super.getPackageManager(); 157 } 158 getCurrentUserHandle()159 protected UserHandle getCurrentUserHandle() { 160 return mMultiProfilePagerAdapter.getCurrentUserHandle(); 161 } 162 163 @Override getWorkProfileUserHandle()164 protected UserHandle getWorkProfileUserHandle() { 165 return sOverrides.workProfileUserHandle; 166 } 167 168 @Override getCloneProfileUserHandle()169 protected UserHandle getCloneProfileUserHandle() { 170 return sOverrides.cloneProfileUserHandle; 171 } 172 173 @Override startActivityAsUser(Intent intent, Bundle options, UserHandle user)174 public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { 175 super.startActivityAsUser(intent, options, user); 176 } 177 178 @Override getResolverRankerServiceUserHandleListInternal(UserHandle userHandle)179 protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle 180 userHandle) { 181 return super.getResolverRankerServiceUserHandleListInternal(userHandle); 182 } 183 184 /** 185 * We cannot directly mock the activity created since instrumentation creates it. 186 * <p> 187 * Instead, we use static instances of this object to modify behavior. 188 */ 189 static class OverrideData { 190 @SuppressWarnings("Since15") 191 public Function<PackageManager, PackageManager> createPackageManager; 192 public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback; 193 public ResolverListController resolverListController; 194 public ResolverListController workResolverListController; 195 public Boolean isVoiceInteraction; 196 public UserHandle workProfileUserHandle; 197 public UserHandle cloneProfileUserHandle; 198 public UserHandle tabOwnerUserHandleForLaunch; 199 public Integer myUserId; 200 public boolean hasCrossProfileIntents; 201 public boolean isQuietModeEnabled; 202 public WorkProfileAvailabilityManager mWorkProfileAvailability; 203 public CrossProfileIntentsChecker mCrossProfileIntentsChecker; 204 reset()205 public void reset() { 206 onSafelyStartInternalCallback = null; 207 isVoiceInteraction = null; 208 createPackageManager = null; 209 resolverListController = mock(ResolverListController.class); 210 workResolverListController = mock(ResolverListController.class); 211 workProfileUserHandle = null; 212 cloneProfileUserHandle = null; 213 tabOwnerUserHandleForLaunch = null; 214 myUserId = null; 215 hasCrossProfileIntents = true; 216 isQuietModeEnabled = false; 217 218 mWorkProfileAvailability = new WorkProfileAvailabilityManager(null, null, null) { 219 @Override 220 public boolean isQuietModeEnabled() { 221 return isQuietModeEnabled; 222 } 223 224 @Override 225 public boolean isWorkProfileUserUnlocked() { 226 return true; 227 } 228 229 @Override 230 public void requestQuietModeEnabled(boolean enabled) { 231 isQuietModeEnabled = enabled; 232 } 233 234 @Override 235 public void markWorkProfileEnabledBroadcastReceived() {} 236 237 @Override 238 public boolean isWaitingToEnableWorkProfile() { 239 return false; 240 } 241 }; 242 243 mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class); 244 when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt())) 245 .thenAnswer(invocation -> hasCrossProfileIntents); 246 } 247 } 248 249 private static class TargetDataLoaderWrapper extends TargetDataLoader { 250 private final TargetDataLoader mTargetDataLoader; 251 private final CountingIdlingResource mLabelIdlingResource; 252 TargetDataLoaderWrapper( TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource)253 private TargetDataLoaderWrapper( 254 TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource) { 255 mTargetDataLoader = targetDataLoader; 256 mLabelIdlingResource = labelIdlingResource; 257 } 258 259 @Override loadAppTargetIcon( @onNull DisplayResolveInfo info, @NonNull UserHandle userHandle, @NonNull Consumer<Drawable> callback)260 public void loadAppTargetIcon( 261 @NonNull DisplayResolveInfo info, 262 @NonNull UserHandle userHandle, 263 @NonNull Consumer<Drawable> callback) { 264 mTargetDataLoader.loadAppTargetIcon(info, userHandle, callback); 265 } 266 267 @Override loadDirectShareIcon( @onNull SelectableTargetInfo info, @NonNull UserHandle userHandle, @NonNull Consumer<Drawable> callback)268 public void loadDirectShareIcon( 269 @NonNull SelectableTargetInfo info, 270 @NonNull UserHandle userHandle, 271 @NonNull Consumer<Drawable> callback) { 272 mTargetDataLoader.loadDirectShareIcon(info, userHandle, callback); 273 } 274 275 @Override loadLabel( @onNull DisplayResolveInfo info, @NonNull Consumer<CharSequence[]> callback)276 public void loadLabel( 277 @NonNull DisplayResolveInfo info, 278 @NonNull Consumer<CharSequence[]> callback) { 279 mLabelIdlingResource.increment(); 280 mTargetDataLoader.loadLabel( 281 info, 282 (result) -> { 283 mLabelIdlingResource.decrement(); 284 callback.accept(result); 285 }); 286 } 287 288 @NonNull 289 @Override createPresentationGetter(@onNull ResolveInfo info)290 public TargetPresentationGetter createPresentationGetter(@NonNull ResolveInfo info) { 291 return mTargetDataLoader.createPresentationGetter(info); 292 } 293 } 294 } 295