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.ResolveInfo;
28 import android.graphics.drawable.Drawable;
29 import android.os.Bundle;
30 import android.os.UserHandle;
31 import android.util.Pair;
32 
33 import androidx.annotation.NonNull;
34 import androidx.test.espresso.idling.CountingIdlingResource;
35 
36 import com.android.intentresolver.chooser.DisplayResolveInfo;
37 import com.android.intentresolver.chooser.SelectableTargetInfo;
38 import com.android.intentresolver.chooser.TargetInfo;
39 import com.android.intentresolver.emptystate.CrossProfileIntentsChecker;
40 import com.android.intentresolver.icons.LabelInfo;
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 
getLabelIdlingResource()56     public CountingIdlingResource getLabelIdlingResource() {
57         return mLabelIdlingResource;
58     }
59 
60     @Override
createResolverListAdapter( Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, UserHandle userHandle)61     public ResolverListAdapter createResolverListAdapter(
62             Context context,
63             List<Intent> payloadIntents,
64             Intent[] initialIntents,
65             List<ResolveInfo> rList,
66             boolean filterLastUsed,
67             UserHandle userHandle) {
68         return new ResolverListAdapter(
69                 context,
70                 payloadIntents,
71                 initialIntents,
72                 rList,
73                 filterLastUsed,
74                 createListController(userHandle),
75                 userHandle,
76                 payloadIntents.get(0),  // TODO: extract upstream
77                 this,
78                 userHandle,
79                 new TargetDataLoaderWrapper(mTargetDataLoader, mLabelIdlingResource));
80     }
81 
82     @Override
createCrossProfileIntentsChecker()83     protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
84         if (sOverrides.mCrossProfileIntentsChecker != null) {
85             return sOverrides.mCrossProfileIntentsChecker;
86         }
87         return super.createCrossProfileIntentsChecker();
88     }
89 
getAdapter()90     ResolverListAdapter getAdapter() {
91         return mMultiProfilePagerAdapter.getActiveListAdapter();
92     }
93 
getPersonalListAdapter()94     ResolverListAdapter getPersonalListAdapter() {
95         return mMultiProfilePagerAdapter.getPersonalListAdapter();
96     }
97 
getWorkListAdapter()98     ResolverListAdapter getWorkListAdapter() {
99         return mMultiProfilePagerAdapter.getWorkListAdapter();
100     }
101 
102     @Override
isVoiceInteraction()103     public boolean isVoiceInteraction() {
104         if (sOverrides.isVoiceInteraction != null) {
105             return sOverrides.isVoiceInteraction;
106         }
107         return super.isVoiceInteraction();
108     }
109 
110     @Override
safelyStartActivityInternal(TargetInfo cti, UserHandle user, @Nullable Bundle options)111     public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
112             @Nullable Bundle options) {
113         if (sOverrides.onSafelyStartInternalCallback != null
114                 && sOverrides.onSafelyStartInternalCallback.apply(new Pair<>(cti, user))) {
115             return;
116         }
117         super.safelyStartActivityInternal(cti, user, options);
118     }
119 
120     @Override
createListController(UserHandle userHandle)121     protected ResolverListController createListController(UserHandle userHandle) {
122         if (userHandle == UserHandle.SYSTEM) {
123             return sOverrides.resolverListController;
124         }
125         return sOverrides.workResolverListController;
126     }
127 
getCurrentUserHandle()128     protected UserHandle getCurrentUserHandle() {
129         return mMultiProfilePagerAdapter.getCurrentUserHandle();
130     }
131 
132     @Override
startActivityAsUser(Intent intent, Bundle options, UserHandle user)133     public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
134         super.startActivityAsUser(intent, options, user);
135     }
136 
137     /**
138      * We cannot directly mock the activity created since instrumentation creates it.
139      * <p>
140      * Instead, we use static instances of this object to modify behavior.
141      */
142     public static class OverrideData {
143         @SuppressWarnings("Since15")
144         public Function<Pair<TargetInfo, UserHandle>, Boolean> onSafelyStartInternalCallback;
145         public ResolverListController resolverListController;
146         public ResolverListController workResolverListController;
147         public Boolean isVoiceInteraction;
148         public boolean hasCrossProfileIntents;
149         public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
150 
reset()151         public void reset() {
152             onSafelyStartInternalCallback = null;
153             isVoiceInteraction = null;
154             resolverListController = mock(ResolverListController.class);
155             workResolverListController = mock(ResolverListController.class);
156             hasCrossProfileIntents = true;
157             mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
158             when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
159                     .thenAnswer(invocation -> hasCrossProfileIntents);
160         }
161     }
162 
163     private static class TargetDataLoaderWrapper extends TargetDataLoader {
164         private final TargetDataLoader mTargetDataLoader;
165         private final CountingIdlingResource mLabelIdlingResource;
166 
TargetDataLoaderWrapper( TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource)167         private TargetDataLoaderWrapper(
168                 TargetDataLoader targetDataLoader, CountingIdlingResource labelIdlingResource) {
169             mTargetDataLoader = targetDataLoader;
170             mLabelIdlingResource = labelIdlingResource;
171         }
172 
173         @Override
174         @Nullable
getOrLoadAppTargetIcon( @onNull DisplayResolveInfo info, @NonNull UserHandle userHandle, @NonNull Consumer<Drawable> callback)175         public Drawable getOrLoadAppTargetIcon(
176                 @NonNull DisplayResolveInfo info,
177                 @NonNull UserHandle userHandle,
178                 @NonNull Consumer<Drawable> callback) {
179             return mTargetDataLoader.getOrLoadAppTargetIcon(info, userHandle, callback);
180         }
181 
182         @Override
183         @Nullable
getOrLoadDirectShareIcon( @onNull SelectableTargetInfo info, @NonNull UserHandle userHandle, @NonNull Consumer<Drawable> callback)184         public Drawable getOrLoadDirectShareIcon(
185                 @NonNull SelectableTargetInfo info,
186                 @NonNull UserHandle userHandle,
187                 @NonNull Consumer<Drawable> callback) {
188             return mTargetDataLoader.getOrLoadDirectShareIcon(info, userHandle, callback);
189         }
190 
191         @Override
loadLabel( @onNull DisplayResolveInfo info, @NonNull Consumer<LabelInfo> callback)192         public void loadLabel(
193                 @NonNull DisplayResolveInfo info,
194                 @NonNull Consumer<LabelInfo> callback) {
195             mLabelIdlingResource.increment();
196             mTargetDataLoader.loadLabel(
197                     info,
198                     (result) -> {
199                         mLabelIdlingResource.decrement();
200                         callback.accept(result);
201                     });
202         }
203 
204         @Override
getOrLoadLabel(@onNull DisplayResolveInfo info)205         public void getOrLoadLabel(@NonNull DisplayResolveInfo info) {
206             mTargetDataLoader.getOrLoadLabel(info);
207         }
208     }
209 }
210