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 package com.android.launcher3.allapps; 17 18 import android.content.Context; 19 import android.view.LayoutInflater; 20 import android.view.View; 21 import android.view.View.OnClickListener; 22 import android.view.View.OnFocusChangeListener; 23 import android.view.View.OnLongClickListener; 24 import android.view.ViewGroup; 25 import android.widget.TextView; 26 27 import androidx.recyclerview.widget.RecyclerView; 28 29 import com.android.launcher3.BubbleTextView; 30 import com.android.launcher3.R; 31 import com.android.launcher3.Utilities; 32 import com.android.launcher3.allapps.search.SearchAdapterProvider; 33 import com.android.launcher3.config.FeatureFlags; 34 import com.android.launcher3.model.data.AppInfo; 35 import com.android.launcher3.views.ActivityContext; 36 37 /** 38 * Adapter for all the apps. 39 * 40 * @param <T> Type of context inflating all apps. 41 */ 42 public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> extends 43 RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> { 44 45 public static final String TAG = "BaseAllAppsAdapter"; 46 47 // A normal icon 48 public static final int VIEW_TYPE_ICON = 1 << 1; 49 // The message shown when there are no filtered results 50 public static final int VIEW_TYPE_EMPTY_SEARCH = 1 << 2; 51 // A divider that separates the apps list and the search market button 52 public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 3; 53 54 public static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 4; 55 public static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 5; 56 57 public static final int NEXT_ID = 6; 58 59 // Common view type masks 60 public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER; 61 public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON; 62 63 protected final SearchAdapterProvider<?> mAdapterProvider; 64 65 /** 66 * ViewHolder for each icon. 67 */ 68 public static class ViewHolder extends RecyclerView.ViewHolder { 69 ViewHolder(View v)70 public ViewHolder(View v) { 71 super(v); 72 } 73 } 74 75 /** Sets the number of apps to be displayed in one row of the all apps screen. */ setAppsPerRow(int appsPerRow)76 public abstract void setAppsPerRow(int appsPerRow); 77 78 /** 79 * Info about a particular adapter item (can be either section or app) 80 */ 81 public static class AdapterItem { 82 /** Common properties */ 83 // The type of this item 84 public final int viewType; 85 86 // The row that this item shows up on 87 public int rowIndex; 88 // The index of this app in the row 89 public int rowAppIndex; 90 // The associated ItemInfoWithIcon for the item 91 public AppInfo itemInfo = null; 92 AdapterItem(int viewType)93 public AdapterItem(int viewType) { 94 this.viewType = viewType; 95 } 96 97 /** 98 * Factory method for AppIcon AdapterItem 99 */ asApp(AppInfo appInfo)100 public static AdapterItem asApp(AppInfo appInfo) { 101 AdapterItem item = new AdapterItem(VIEW_TYPE_ICON); 102 item.itemInfo = appInfo; 103 return item; 104 } 105 isCountedForAccessibility()106 protected boolean isCountedForAccessibility() { 107 return viewType == VIEW_TYPE_ICON; 108 } 109 110 /** 111 * Returns true if the items represent the same object 112 */ isSameAs(AdapterItem other)113 public boolean isSameAs(AdapterItem other) { 114 return (other.viewType == viewType) && (other.getClass() == getClass()); 115 } 116 117 /** 118 * This is called only if {@link #isSameAs} returns true to check if the contents are same 119 * as well. Returning true will prevent redrawing of thee item. 120 */ isContentSame(AdapterItem other)121 public boolean isContentSame(AdapterItem other) { 122 return itemInfo == null && other.itemInfo == null; 123 } 124 125 /** Sets the alpha of the decorator for this item. Returns true if successful. */ setDecorationFillAlpha(int alpha)126 public boolean setDecorationFillAlpha(int alpha) { 127 return false; 128 } 129 } 130 131 protected final T mActivityContext; 132 protected final AlphabeticalAppsList<T> mApps; 133 // The text to show when there are no search results and no market search handler. 134 protected int mAppsPerRow; 135 136 protected final LayoutInflater mLayoutInflater; 137 protected final OnClickListener mOnIconClickListener; 138 protected final OnLongClickListener mOnIconLongClickListener; 139 protected OnFocusChangeListener mIconFocusListener; 140 private final int mExtraTextHeight; 141 BaseAllAppsAdapter(T activityContext, LayoutInflater inflater, AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider)142 public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater, 143 AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) { 144 mActivityContext = activityContext; 145 mApps = apps; 146 mLayoutInflater = inflater; 147 148 mOnIconClickListener = mActivityContext.getItemOnClickListener(); 149 mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener(); 150 151 mAdapterProvider = adapterProvider; 152 mExtraTextHeight = Utilities.calculateTextHeight( 153 mActivityContext.getDeviceProfile().allAppsIconTextSizePx); 154 } 155 156 /** Checks if the passed viewType represents all apps divider. */ isDividerViewType(int viewType)157 public static boolean isDividerViewType(int viewType) { 158 return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER); 159 } 160 161 /** Checks if the passed viewType represents all apps icon. */ isIconViewType(int viewType)162 public static boolean isIconViewType(int viewType) { 163 return isViewType(viewType, VIEW_TYPE_MASK_ICON); 164 } 165 setIconFocusListener(OnFocusChangeListener focusListener)166 public void setIconFocusListener(OnFocusChangeListener focusListener) { 167 mIconFocusListener = focusListener; 168 } 169 170 /** 171 * Returns the layout manager. 172 */ getLayoutManager()173 public abstract RecyclerView.LayoutManager getLayoutManager(); 174 175 @Override onCreateViewHolder(ViewGroup parent, int viewType)176 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 177 switch (viewType) { 178 case VIEW_TYPE_ICON: 179 int layout = !FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get() ? R.layout.all_apps_icon 180 : R.layout.all_apps_icon_twoline; 181 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( 182 layout, parent, false); 183 icon.setLongPressTimeoutFactor(1f); 184 icon.setOnFocusChangeListener(mIconFocusListener); 185 icon.setOnClickListener(mOnIconClickListener); 186 icon.setOnLongClickListener(mOnIconLongClickListener); 187 // Ensure the all apps icon height matches the workspace icons in portrait mode. 188 icon.getLayoutParams().height = 189 mActivityContext.getDeviceProfile().allAppsCellHeightPx; 190 if (FeatureFlags.ENABLE_TWOLINE_ALLAPPS.get()) { 191 icon.getLayoutParams().height += mExtraTextHeight; 192 } 193 return new ViewHolder(icon); 194 case VIEW_TYPE_EMPTY_SEARCH: 195 return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, 196 parent, false)); 197 case VIEW_TYPE_ALL_APPS_DIVIDER: 198 return new ViewHolder(mLayoutInflater.inflate( 199 R.layout.all_apps_divider, parent, false)); 200 case VIEW_TYPE_WORK_EDU_CARD: 201 return new ViewHolder(mLayoutInflater.inflate( 202 R.layout.work_apps_edu, parent, false)); 203 case VIEW_TYPE_WORK_DISABLED_CARD: 204 return new ViewHolder(mLayoutInflater.inflate( 205 R.layout.work_apps_paused, parent, false)); 206 default: 207 if (mAdapterProvider.isViewSupported(viewType)) { 208 return mAdapterProvider.onCreateViewHolder(mLayoutInflater, parent, viewType); 209 } 210 throw new RuntimeException("Unexpected view type" + viewType); 211 } 212 } 213 214 @Override onBindViewHolder(ViewHolder holder, int position)215 public void onBindViewHolder(ViewHolder holder, int position) { 216 switch (holder.getItemViewType()) { 217 case VIEW_TYPE_ICON: { 218 AdapterItem adapterItem = mApps.getAdapterItems().get(position); 219 BubbleTextView icon = (BubbleTextView) holder.itemView; 220 icon.reset(); 221 icon.applyFromApplicationInfo(adapterItem.itemInfo); 222 break; 223 } 224 case VIEW_TYPE_EMPTY_SEARCH: { 225 AppInfo info = mApps.getAdapterItems().get(position).itemInfo; 226 if (info != null) { 227 ((TextView) holder.itemView).setText(mActivityContext.getString( 228 R.string.all_apps_no_search_results, info.title)); 229 } 230 break; 231 } 232 case VIEW_TYPE_ALL_APPS_DIVIDER: 233 case VIEW_TYPE_WORK_DISABLED_CARD: 234 // nothing to do 235 break; 236 case VIEW_TYPE_WORK_EDU_CARD: 237 ((WorkEduCard) holder.itemView).setPosition(position); 238 break; 239 default: 240 if (mAdapterProvider.isViewSupported(holder.getItemViewType())) { 241 mAdapterProvider.onBindView(holder, position); 242 } 243 } 244 } 245 246 @Override onFailedToRecycleView(ViewHolder holder)247 public boolean onFailedToRecycleView(ViewHolder holder) { 248 // Always recycle and we will reset the view when it is bound 249 return true; 250 } 251 252 @Override getItemCount()253 public int getItemCount() { 254 return mApps.getAdapterItems().size(); 255 } 256 257 @Override getItemViewType(int position)258 public int getItemViewType(int position) { 259 AdapterItem item = mApps.getAdapterItems().get(position); 260 return item.viewType; 261 } 262 isViewType(int viewType, int viewTypeMask)263 protected static boolean isViewType(int viewType, int viewTypeMask) { 264 return (viewType & viewTypeMask) != 0; 265 } 266 267 } 268