blob: ca587c16f08b40c2d58d51d1c129aa8067fdfffb [file] [log] [blame]
/*
* 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.launcher3.allapps;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_LEFT;
import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_RIGHT;
import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.views.ActivityContext;
/**
* Adapter for all the apps.
*
* @param <T> Type of context inflating all apps.
*/
public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> extends
RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> {
public static final String TAG = "BaseAllAppsAdapter";
// A normal icon
public static final int VIEW_TYPE_ICON = 1 << 1;
// The message shown when there are no filtered results
public static final int VIEW_TYPE_EMPTY_SEARCH = 1 << 2;
// A divider that separates the apps list and the search market button
public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 3;
public static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 4;
public static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 5;
public static final int VIEW_TYPE_PRIVATE_SPACE_HEADER = 1 << 6;
public static final int VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER = 1 << 7;
public static final int NEXT_ID = 8;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER =
VIEW_TYPE_PRIVATE_SPACE_HEADER;
public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_SYS_APPS_DIVIDER =
VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER;
protected final SearchAdapterProvider<?> mAdapterProvider;
/**
* ViewHolder for each icon.
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View v) {
super(v);
}
}
/** Sets the number of apps to be displayed in one row of the all apps screen. */
public abstract void setAppsPerRow(int appsPerRow);
/**
* Info about a particular adapter item (can be either section or app)
*/
public static class AdapterItem {
/** Common properties */
// The type of this item
public final int viewType;
// The row that this item shows up on
public int rowIndex;
// The index of this app in the row
public int rowAppIndex;
// The associated ItemInfoWithIcon for the item
public AppInfo itemInfo = null;
// Private App Decorator
public SectionDecorationInfo decorationInfo = null;
public AdapterItem(int viewType) {
this.viewType = viewType;
}
/**
* Factory method for AppIcon AdapterItem
*/
public static AdapterItem asApp(AppInfo appInfo) {
AdapterItem item = new AdapterItem(VIEW_TYPE_ICON);
item.itemInfo = appInfo;
return item;
}
public static AdapterItem asAppWithDecorationInfo(AppInfo appInfo,
SectionDecorationInfo decorationInfo) {
AdapterItem item = asApp(appInfo);
item.decorationInfo = decorationInfo;
return item;
}
protected boolean isCountedForAccessibility() {
return viewType == VIEW_TYPE_ICON;
}
/**
* Returns true if the items represent the same object
*/
public boolean isSameAs(AdapterItem other) {
return (other.viewType == viewType) && (other.getClass() == getClass());
}
/**
* This is called only if {@link #isSameAs} returns true to check if the contents are same
* as well. Returning true will prevent redrawing of thee item.
*/
public boolean isContentSame(AdapterItem other) {
return itemInfo == null && other.itemInfo == null;
}
@Nullable
public SectionDecorationInfo getDecorationInfo() {
return decorationInfo;
}
/** Sets the alpha of the decorator for this item. */
protected void setDecorationFillAlpha(int alpha) {
if (decorationInfo == null || decorationInfo.getDecorationHandler() == null) {
return;
}
decorationInfo.getDecorationHandler().setFillAlpha(alpha);
}
}
protected final T mActivityContext;
protected final AlphabeticalAppsList<T> mApps;
// The text to show when there are no search results and no market search handler.
protected int mAppsPerRow;
protected final LayoutInflater mLayoutInflater;
protected final OnClickListener mOnIconClickListener;
protected final OnLongClickListener mOnIconLongClickListener;
protected OnFocusChangeListener mIconFocusListener;
private final PrivateSpaceHeaderViewController mPrivateSpaceHeaderViewController;
public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) {
this(activityContext, inflater, apps, adapterProvider, null);
}
public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater,
AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider,
PrivateSpaceHeaderViewController privateSpaceHeaderViewController) {
mActivityContext = activityContext;
mApps = apps;
mLayoutInflater = inflater;
mOnIconClickListener = mActivityContext.getItemOnClickListener();
mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener();
mAdapterProvider = adapterProvider;
mPrivateSpaceHeaderViewController = privateSpaceHeaderViewController;
}
/** Checks if the passed viewType represents all apps divider. */
public static boolean isDividerViewType(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
}
/** Checks if the passed viewType represents all apps icon. */
public static boolean isIconViewType(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_ICON);
}
/** Checks if the passed viewType represents private space header. */
public static boolean isPrivateSpaceHeaderView(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER);
}
/** Checks if the passed viewType represents private space system apps divider. */
public static boolean isPrivateSpaceSysAppsDividerView(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_PRIVATE_SPACE_SYS_APPS_DIVIDER);
}
public void setIconFocusListener(OnFocusChangeListener focusListener) {
mIconFocusListener = focusListener;
}
/**
* Returns the layout manager.
*/
public abstract RecyclerView.LayoutManager getLayoutManager();
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_ICON:
int layout = (FeatureFlags.enableTwolineAllapps() &&
(!Flags.enableTwolineToggle() || (Flags.enableTwolineToggle()
&& LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(
mActivityContext.getApplicationContext()))))
? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon;
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
layout, parent, false);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
icon.setOnClickListener(mOnIconClickListener);
icon.setOnLongClickListener(mOnIconLongClickListener);
// Ensure the all apps icon height matches the workspace icons in portrait mode.
icon.getLayoutParams().height =
mActivityContext.getDeviceProfile().allAppsCellHeightPx;
return new ViewHolder(icon);
case VIEW_TYPE_EMPTY_SEARCH:
return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
parent, false));
case VIEW_TYPE_ALL_APPS_DIVIDER, VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.private_space_divider, parent, false));
case VIEW_TYPE_WORK_EDU_CARD:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.work_apps_edu, parent, false));
case VIEW_TYPE_WORK_DISABLED_CARD:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.work_apps_paused, parent, false));
case VIEW_TYPE_PRIVATE_SPACE_HEADER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.private_space_header, parent, false));
default:
if (mAdapterProvider.isViewSupported(viewType)) {
return mAdapterProvider.onCreateViewHolder(mLayoutInflater, parent, viewType);
}
throw new RuntimeException("Unexpected view type" + viewType);
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemView.setVisibility(View.VISIBLE);
switch (holder.getItemViewType()) {
case VIEW_TYPE_ICON: {
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
BubbleTextView icon = (BubbleTextView) holder.itemView;
icon.reset();
icon.applyFromApplicationInfo(adapterItem.itemInfo);
icon.setOnFocusChangeListener(mIconFocusListener);
break;
}
case VIEW_TYPE_EMPTY_SEARCH: {
AppInfo info = mApps.getAdapterItems().get(position).itemInfo;
if (info != null) {
((TextView) holder.itemView).setText(mActivityContext.getString(
R.string.all_apps_no_search_results, info.title));
}
break;
}
case VIEW_TYPE_PRIVATE_SPACE_HEADER:
RelativeLayout psHeaderLayout = holder.itemView.findViewById(
R.id.ps_header_layout);
assert mPrivateSpaceHeaderViewController != null;
assert psHeaderLayout != null;
mPrivateSpaceHeaderViewController.addPrivateSpaceHeaderViewElements(psHeaderLayout);
AdapterItem adapterItem = mApps.getAdapterItems().get(position);
int roundRegions = ROUND_TOP_LEFT | ROUND_TOP_RIGHT;
if (mPrivateSpaceHeaderViewController.getPrivateProfileManager().getCurrentState()
== STATE_DISABLED) {
roundRegions |= (ROUND_BOTTOM_LEFT | ROUND_BOTTOM_RIGHT);
}
adapterItem.decorationInfo =
new SectionDecorationInfo(mActivityContext, roundRegions,
false /* decorateTogether */);
break;
case VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER:
adapterItem = mApps.getAdapterItems().get(position);
adapterItem.decorationInfo = new SectionDecorationInfo(mActivityContext,
ROUND_NOTHING, true /* decorateTogether */);
break;
case VIEW_TYPE_ALL_APPS_DIVIDER:
case VIEW_TYPE_WORK_DISABLED_CARD:
// nothing to do
break;
case VIEW_TYPE_WORK_EDU_CARD:
((WorkEduCard) holder.itemView).setPosition(position);
break;
default:
if (mAdapterProvider.isViewSupported(holder.getItemViewType())) {
mAdapterProvider.onBindView(holder, position);
}
}
}
@Override
public boolean onFailedToRecycleView(ViewHolder holder) {
// Always recycle and we will reset the view when it is bound
return true;
}
@Override
public int getItemCount() {
return mApps.getAdapterItems().size();
}
@Override
public int getItemViewType(int position) {
AdapterItem item = mApps.getAdapterItems().get(position);
return item.viewType;
}
protected static boolean isViewType(int viewType, int viewTypeMask) {
return (viewType & viewTypeMask) != 0;
}
}