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 package com.android.intentresolver; 18 19 import android.annotation.Nullable; 20 import android.content.Context; 21 import android.os.UserHandle; 22 import android.util.Log; 23 import android.view.View; 24 import android.view.ViewGroup; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import com.google.common.collect.ImmutableList; 29 30 import java.util.Optional; 31 import java.util.function.Function; 32 import java.util.function.Supplier; 33 34 /** 35 * Implementation of {@link AbstractMultiProfilePagerAdapter} that consolidates the variation in 36 * existing implementations; most overrides were only to vary type signatures (which are better 37 * represented via generic types), and a few minor behavioral customizations are now implemented 38 * through small injectable delegate classes. 39 * TODO: now that the existing implementations are shown to be expressible in terms of this new 40 * generic type, merge up into the base class and simplify the public APIs. 41 * TODO: attempt to further restrict visibility in the methods we expose. 42 * TODO: deprecate and audit/fix usages of any methods that refer to the "active" or "inactive" 43 * adapters; these were marked {@link VisibleForTesting} and their usage seems like an accident 44 * waiting to happen since clients seem to make assumptions about which adapter will be "active" in 45 * a particular context, and more explicit APIs would make sure those were valid. 46 * TODO: consider renaming legacy methods (e.g. why do we know it's a "list", not just a "page"?) 47 * 48 * @param <PageViewT> the type of the widget that represents the contents of a page in this adapter 49 * @param <SinglePageAdapterT> the type of a "root" adapter class to be instantiated and included in 50 * the per-profile records. 51 * @param <ListAdapterT> the concrete type of a {@link ResolverListAdapter} implementation to 52 * control the contents of a given per-profile list. This is provided for convenience, since it must 53 * be possible to get the list adapter from the page adapter via our {@link mListAdapterExtractor}. 54 * 55 * TODO: this class doesn't make any explicit usage of the {@link ResolverListAdapter} API, so the 56 * type constraint can probably be dropped once the API is merged upwards and cleaned. 57 */ 58 class GenericMultiProfilePagerAdapter< 59 PageViewT extends ViewGroup, 60 SinglePageAdapterT, 61 ListAdapterT extends ResolverListAdapter> extends AbstractMultiProfilePagerAdapter { 62 63 /** Delegate to set up a given adapter and page view to be used together. */ 64 public interface AdapterBinder<PageViewT, SinglePageAdapterT> { 65 /** 66 * The given {@code view} will be associated with the given {@code adapter}. Do any work 67 * necessary to configure them compatibly, introduce them to each other, etc. 68 */ bind(PageViewT view, SinglePageAdapterT adapter)69 void bind(PageViewT view, SinglePageAdapterT adapter); 70 } 71 72 private final Function<SinglePageAdapterT, ListAdapterT> mListAdapterExtractor; 73 private final AdapterBinder<PageViewT, SinglePageAdapterT> mAdapterBinder; 74 private final Supplier<ViewGroup> mPageViewInflater; 75 private final Supplier<Optional<Integer>> mContainerBottomPaddingOverrideSupplier; 76 77 private final ImmutableList<GenericProfileDescriptor<PageViewT, SinglePageAdapterT>> mItems; 78 GenericMultiProfilePagerAdapter( Context context, Function<SinglePageAdapterT, ListAdapterT> listAdapterExtractor, AdapterBinder<PageViewT, SinglePageAdapterT> adapterBinder, ImmutableList<SinglePageAdapterT> adapters, EmptyStateProvider emptyStateProvider, Supplier<Boolean> workProfileQuietModeChecker, @Profile int defaultProfile, UserHandle workProfileUserHandle, UserHandle cloneProfileUserHandle, Supplier<ViewGroup> pageViewInflater, Supplier<Optional<Integer>> containerBottomPaddingOverrideSupplier)79 GenericMultiProfilePagerAdapter( 80 Context context, 81 Function<SinglePageAdapterT, ListAdapterT> listAdapterExtractor, 82 AdapterBinder<PageViewT, SinglePageAdapterT> adapterBinder, 83 ImmutableList<SinglePageAdapterT> adapters, 84 EmptyStateProvider emptyStateProvider, 85 Supplier<Boolean> workProfileQuietModeChecker, 86 @Profile int defaultProfile, 87 UserHandle workProfileUserHandle, 88 UserHandle cloneProfileUserHandle, 89 Supplier<ViewGroup> pageViewInflater, 90 Supplier<Optional<Integer>> containerBottomPaddingOverrideSupplier) { 91 super( 92 context, 93 /* currentPage= */ defaultProfile, 94 emptyStateProvider, 95 workProfileQuietModeChecker, 96 workProfileUserHandle, 97 cloneProfileUserHandle); 98 99 mListAdapterExtractor = listAdapterExtractor; 100 mAdapterBinder = adapterBinder; 101 mPageViewInflater = pageViewInflater; 102 mContainerBottomPaddingOverrideSupplier = containerBottomPaddingOverrideSupplier; 103 104 ImmutableList.Builder<GenericProfileDescriptor<PageViewT, SinglePageAdapterT>> items = 105 new ImmutableList.Builder<>(); 106 for (SinglePageAdapterT adapter : adapters) { 107 items.add(createProfileDescriptor(adapter)); 108 } 109 mItems = items.build(); 110 } 111 112 private GenericProfileDescriptor<PageViewT, SinglePageAdapterT> createProfileDescriptor(SinglePageAdapterT adapter)113 createProfileDescriptor(SinglePageAdapterT adapter) { 114 return new GenericProfileDescriptor<>(mPageViewInflater.get(), adapter); 115 } 116 117 @Override getItem(int pageIndex)118 protected GenericProfileDescriptor<PageViewT, SinglePageAdapterT> getItem(int pageIndex) { 119 return mItems.get(pageIndex); 120 } 121 122 @Override getItemCount()123 public int getItemCount() { 124 return mItems.size(); 125 } 126 getListViewForIndex(int index)127 public PageViewT getListViewForIndex(int index) { 128 return getItem(index).mView; 129 } 130 131 @Override 132 @VisibleForTesting getAdapterForIndex(int index)133 public SinglePageAdapterT getAdapterForIndex(int index) { 134 return getItem(index).mAdapter; 135 } 136 137 @Override setupListAdapter(int pageIndex)138 protected void setupListAdapter(int pageIndex) { 139 mAdapterBinder.bind(getListViewForIndex(pageIndex), getAdapterForIndex(pageIndex)); 140 } 141 142 @Override instantiateItem(ViewGroup container, int position)143 public ViewGroup instantiateItem(ViewGroup container, int position) { 144 setupListAdapter(position); 145 return super.instantiateItem(container, position); 146 } 147 148 @Override 149 @Nullable getListAdapterForUserHandle(UserHandle userHandle)150 protected ListAdapterT getListAdapterForUserHandle(UserHandle userHandle) { 151 if (getPersonalListAdapter().getUserHandle().equals(userHandle) 152 || userHandle.equals(getCloneUserHandle())) { 153 return getPersonalListAdapter(); 154 } else if (getWorkListAdapter() != null 155 && getWorkListAdapter().getUserHandle().equals(userHandle)) { 156 return getWorkListAdapter(); 157 } 158 return null; 159 } 160 161 @Override 162 @VisibleForTesting getActiveListAdapter()163 public ListAdapterT getActiveListAdapter() { 164 return mListAdapterExtractor.apply(getAdapterForIndex(getCurrentPage())); 165 } 166 167 @Override 168 @VisibleForTesting getInactiveListAdapter()169 public ListAdapterT getInactiveListAdapter() { 170 if (getCount() < 2) { 171 return null; 172 } 173 return mListAdapterExtractor.apply(getAdapterForIndex(1 - getCurrentPage())); 174 } 175 176 @Override getPersonalListAdapter()177 public ListAdapterT getPersonalListAdapter() { 178 return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_PERSONAL)); 179 } 180 181 @Override getWorkListAdapter()182 public ListAdapterT getWorkListAdapter() { 183 if (!hasAdapterForIndex(PROFILE_WORK)) { 184 return null; 185 } 186 return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_WORK)); 187 } 188 189 @Override getCurrentRootAdapter()190 protected SinglePageAdapterT getCurrentRootAdapter() { 191 return getAdapterForIndex(getCurrentPage()); 192 } 193 194 @Override getActiveAdapterView()195 protected PageViewT getActiveAdapterView() { 196 return getListViewForIndex(getCurrentPage()); 197 } 198 199 @Override getInactiveAdapterView()200 protected PageViewT getInactiveAdapterView() { 201 if (getCount() < 2) { 202 return null; 203 } 204 return getListViewForIndex(1 - getCurrentPage()); 205 } 206 207 @Override setupContainerPadding(View container)208 protected void setupContainerPadding(View container) { 209 Optional<Integer> bottomPaddingOverride = mContainerBottomPaddingOverrideSupplier.get(); 210 bottomPaddingOverride.ifPresent(paddingBottom -> 211 container.setPadding( 212 container.getPaddingLeft(), 213 container.getPaddingTop(), 214 container.getPaddingRight(), 215 paddingBottom)); 216 } 217 hasAdapterForIndex(int pageIndex)218 private boolean hasAdapterForIndex(int pageIndex) { 219 return (pageIndex < getCount()); 220 } 221 222 // TODO: `ChooserActivity` also has a per-profile record type. Maybe the "multi-profile pager" 223 // should be the owner of all per-profile data (especially now that the API is generic)? 224 private static class GenericProfileDescriptor<PageViewT, SinglePageAdapterT> extends 225 ProfileDescriptor { 226 private final SinglePageAdapterT mAdapter; 227 private final PageViewT mView; 228 GenericProfileDescriptor(ViewGroup rootView, SinglePageAdapterT adapter)229 GenericProfileDescriptor(ViewGroup rootView, SinglePageAdapterT adapter) { 230 super(rootView); 231 mAdapter = adapter; 232 mView = (PageViewT) rootView.findViewById(com.android.internal.R.id.resolver_list); 233 } 234 } 235 } 236