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