| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.intentresolver; |
| |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.os.UserHandle; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.ViewGroup; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import com.google.common.collect.ImmutableList; |
| |
| import java.util.Optional; |
| import java.util.function.Function; |
| import java.util.function.Supplier; |
| |
| /** |
| * Implementation of {@link AbstractMultiProfilePagerAdapter} that consolidates the variation in |
| * existing implementations; most overrides were only to vary type signatures (which are better |
| * represented via generic types), and a few minor behavioral customizations are now implemented |
| * through small injectable delegate classes. |
| * TODO: now that the existing implementations are shown to be expressible in terms of this new |
| * generic type, merge up into the base class and simplify the public APIs. |
| * TODO: attempt to further restrict visibility in the methods we expose. |
| * TODO: deprecate and audit/fix usages of any methods that refer to the "active" or "inactive" |
| * adapters; these were marked {@link VisibleForTesting} and their usage seems like an accident |
| * waiting to happen since clients seem to make assumptions about which adapter will be "active" in |
| * a particular context, and more explicit APIs would make sure those were valid. |
| * TODO: consider renaming legacy methods (e.g. why do we know it's a "list", not just a "page"?) |
| * |
| * @param <PageViewT> the type of the widget that represents the contents of a page in this adapter |
| * @param <SinglePageAdapterT> the type of a "root" adapter class to be instantiated and included in |
| * the per-profile records. |
| * @param <ListAdapterT> the concrete type of a {@link ResolverListAdapter} implementation to |
| * control the contents of a given per-profile list. This is provided for convenience, since it must |
| * be possible to get the list adapter from the page adapter via our {@link mListAdapterExtractor}. |
| * |
| * TODO: this class doesn't make any explicit usage of the {@link ResolverListAdapter} API, so the |
| * type constraint can probably be dropped once the API is merged upwards and cleaned. |
| */ |
| class GenericMultiProfilePagerAdapter< |
| PageViewT extends ViewGroup, |
| SinglePageAdapterT, |
| ListAdapterT extends ResolverListAdapter> extends AbstractMultiProfilePagerAdapter { |
| |
| /** Delegate to set up a given adapter and page view to be used together. */ |
| public interface AdapterBinder<PageViewT, SinglePageAdapterT> { |
| /** |
| * The given {@code view} will be associated with the given {@code adapter}. Do any work |
| * necessary to configure them compatibly, introduce them to each other, etc. |
| */ |
| void bind(PageViewT view, SinglePageAdapterT adapter); |
| } |
| |
| private final Function<SinglePageAdapterT, ListAdapterT> mListAdapterExtractor; |
| private final AdapterBinder<PageViewT, SinglePageAdapterT> mAdapterBinder; |
| private final Supplier<ViewGroup> mPageViewInflater; |
| private final Supplier<Optional<Integer>> mContainerBottomPaddingOverrideSupplier; |
| |
| private final ImmutableList<GenericProfileDescriptor<PageViewT, SinglePageAdapterT>> mItems; |
| |
| 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) { |
| super( |
| context, |
| /* currentPage= */ defaultProfile, |
| emptyStateProvider, |
| workProfileQuietModeChecker, |
| workProfileUserHandle, |
| cloneProfileUserHandle); |
| |
| mListAdapterExtractor = listAdapterExtractor; |
| mAdapterBinder = adapterBinder; |
| mPageViewInflater = pageViewInflater; |
| mContainerBottomPaddingOverrideSupplier = containerBottomPaddingOverrideSupplier; |
| |
| ImmutableList.Builder<GenericProfileDescriptor<PageViewT, SinglePageAdapterT>> items = |
| new ImmutableList.Builder<>(); |
| for (SinglePageAdapterT adapter : adapters) { |
| items.add(createProfileDescriptor(adapter)); |
| } |
| mItems = items.build(); |
| } |
| |
| private GenericProfileDescriptor<PageViewT, SinglePageAdapterT> |
| createProfileDescriptor(SinglePageAdapterT adapter) { |
| return new GenericProfileDescriptor<>(mPageViewInflater.get(), adapter); |
| } |
| |
| @Override |
| protected GenericProfileDescriptor<PageViewT, SinglePageAdapterT> getItem(int pageIndex) { |
| return mItems.get(pageIndex); |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mItems.size(); |
| } |
| |
| public PageViewT getListViewForIndex(int index) { |
| return getItem(index).mView; |
| } |
| |
| @Override |
| @VisibleForTesting |
| public SinglePageAdapterT getAdapterForIndex(int index) { |
| return getItem(index).mAdapter; |
| } |
| |
| @Override |
| protected void setupListAdapter(int pageIndex) { |
| mAdapterBinder.bind(getListViewForIndex(pageIndex), getAdapterForIndex(pageIndex)); |
| } |
| |
| @Override |
| public ViewGroup instantiateItem(ViewGroup container, int position) { |
| setupListAdapter(position); |
| return super.instantiateItem(container, position); |
| } |
| |
| @Override |
| @Nullable |
| protected ListAdapterT getListAdapterForUserHandle(UserHandle userHandle) { |
| if (getPersonalListAdapter().getUserHandle().equals(userHandle) |
| || userHandle.equals(getCloneUserHandle())) { |
| return getPersonalListAdapter(); |
| } else if (getWorkListAdapter() != null |
| && getWorkListAdapter().getUserHandle().equals(userHandle)) { |
| return getWorkListAdapter(); |
| } |
| return null; |
| } |
| |
| @Override |
| @VisibleForTesting |
| public ListAdapterT getActiveListAdapter() { |
| return mListAdapterExtractor.apply(getAdapterForIndex(getCurrentPage())); |
| } |
| |
| @Override |
| @VisibleForTesting |
| public ListAdapterT getInactiveListAdapter() { |
| if (getCount() < 2) { |
| return null; |
| } |
| return mListAdapterExtractor.apply(getAdapterForIndex(1 - getCurrentPage())); |
| } |
| |
| @Override |
| public ListAdapterT getPersonalListAdapter() { |
| return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_PERSONAL)); |
| } |
| |
| @Override |
| public ListAdapterT getWorkListAdapter() { |
| if (!hasAdapterForIndex(PROFILE_WORK)) { |
| return null; |
| } |
| return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_WORK)); |
| } |
| |
| @Override |
| protected SinglePageAdapterT getCurrentRootAdapter() { |
| return getAdapterForIndex(getCurrentPage()); |
| } |
| |
| @Override |
| protected PageViewT getActiveAdapterView() { |
| return getListViewForIndex(getCurrentPage()); |
| } |
| |
| @Override |
| protected PageViewT getInactiveAdapterView() { |
| if (getCount() < 2) { |
| return null; |
| } |
| return getListViewForIndex(1 - getCurrentPage()); |
| } |
| |
| @Override |
| protected void setupContainerPadding(View container) { |
| Optional<Integer> bottomPaddingOverride = mContainerBottomPaddingOverrideSupplier.get(); |
| bottomPaddingOverride.ifPresent(paddingBottom -> |
| container.setPadding( |
| container.getPaddingLeft(), |
| container.getPaddingTop(), |
| container.getPaddingRight(), |
| paddingBottom)); |
| } |
| |
| private boolean hasAdapterForIndex(int pageIndex) { |
| return (pageIndex < getCount()); |
| } |
| |
| // TODO: `ChooserActivity` also has a per-profile record type. Maybe the "multi-profile pager" |
| // should be the owner of all per-profile data (especially now that the API is generic)? |
| private static class GenericProfileDescriptor<PageViewT, SinglePageAdapterT> extends |
| ProfileDescriptor { |
| private final SinglePageAdapterT mAdapter; |
| private final PageViewT mView; |
| |
| GenericProfileDescriptor(ViewGroup rootView, SinglePageAdapterT adapter) { |
| super(rootView); |
| mAdapter = adapter; |
| mView = (PageViewT) rootView.findViewById(com.android.internal.R.id.resolver_list); |
| } |
| } |
| } |