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 android.view.View.GONE; 19 20 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT; 21 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT; 22 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING; 23 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_LEFT; 24 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_TOP_RIGHT; 25 import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED; 26 import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED; 27 28 import android.content.Context; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.View.OnClickListener; 33 import android.view.View.OnFocusChangeListener; 34 import android.view.View.OnLongClickListener; 35 import android.view.ViewGroup; 36 import android.widget.RelativeLayout; 37 import android.widget.TextView; 38 39 import androidx.annotation.Nullable; 40 import androidx.recyclerview.widget.RecyclerView; 41 42 import com.android.launcher3.BubbleTextView; 43 import com.android.launcher3.R; 44 import com.android.launcher3.allapps.search.SearchAdapterProvider; 45 import com.android.launcher3.model.data.AppInfo; 46 import com.android.launcher3.views.ActivityContext; 47 48 /** 49 * Adapter for all the apps. 50 * 51 * @param <T> Type of context inflating all apps. 52 */ 53 public abstract class BaseAllAppsAdapter<T extends Context & ActivityContext> extends 54 RecyclerView.Adapter<BaseAllAppsAdapter.ViewHolder> { 55 56 public static final String TAG = "BaseAllAppsAdapter"; 57 58 // A normal icon 59 public static final int VIEW_TYPE_ICON = 1 << 1; 60 // The message shown when there are no filtered results 61 public static final int VIEW_TYPE_EMPTY_SEARCH = 1 << 2; 62 // A divider that separates the apps list and the search market button 63 public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 3; 64 65 public static final int VIEW_TYPE_WORK_EDU_CARD = 1 << 4; 66 public static final int VIEW_TYPE_WORK_DISABLED_CARD = 1 << 5; 67 public static final int VIEW_TYPE_PRIVATE_SPACE_HEADER = 1 << 6; 68 public static final int VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER = 1 << 7; 69 public static final int VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO = 1 << 8; 70 public static final int NEXT_ID = 9; 71 72 // Common view type masks 73 public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER; 74 public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON; 75 76 public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER = 77 VIEW_TYPE_PRIVATE_SPACE_HEADER; 78 public static final int VIEW_TYPE_MASK_PRIVATE_SPACE_SYS_APPS_DIVIDER = 79 VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER; 80 81 protected final SearchAdapterProvider<?> mAdapterProvider; 82 83 /** 84 * ViewHolder for each icon. 85 */ 86 public static class ViewHolder extends RecyclerView.ViewHolder { 87 ViewHolder(View v)88 public ViewHolder(View v) { 89 super(v); 90 } 91 } 92 93 /** Sets the number of apps to be displayed in one row of the all apps screen. */ setAppsPerRow(int appsPerRow)94 public abstract void setAppsPerRow(int appsPerRow); 95 96 /** 97 * Info about a particular adapter item (can be either section or app) 98 */ 99 public static class AdapterItem { 100 /** Common properties */ 101 // The type of this item 102 public final int viewType; 103 104 // The row that this item shows up on 105 public int rowIndex; 106 // The index of this app in the row 107 public int rowAppIndex; 108 // The associated ItemInfoWithIcon for the item 109 public AppInfo itemInfo = null; 110 // Private App Decorator 111 public SectionDecorationInfo decorationInfo = null; AdapterItem(int viewType)112 public AdapterItem(int viewType) { 113 this.viewType = viewType; 114 } 115 116 /** 117 * Factory method for AppIcon AdapterItem 118 */ asApp(AppInfo appInfo)119 public static AdapterItem asApp(AppInfo appInfo) { 120 AdapterItem item = new AdapterItem(VIEW_TYPE_ICON); 121 item.itemInfo = appInfo; 122 return item; 123 } 124 asAppWithDecorationInfo(AppInfo appInfo, SectionDecorationInfo decorationInfo)125 public static AdapterItem asAppWithDecorationInfo(AppInfo appInfo, 126 SectionDecorationInfo decorationInfo) { 127 AdapterItem item = asApp(appInfo); 128 item.decorationInfo = decorationInfo; 129 return item; 130 } 131 isCountedForAccessibility()132 protected boolean isCountedForAccessibility() { 133 return viewType == VIEW_TYPE_ICON; 134 } 135 136 /** 137 * Returns true if the items represent the same object 138 */ isSameAs(AdapterItem other)139 public boolean isSameAs(AdapterItem other) { 140 return (other.viewType == viewType) && (other.getClass() == getClass()); 141 } 142 143 /** 144 * This is called only if {@link #isSameAs} returns true to check if the contents are same 145 * as well. Returning true will prevent redrawing of thee item. 146 */ isContentSame(AdapterItem other)147 public boolean isContentSame(AdapterItem other) { 148 return itemInfo == null && other.itemInfo == null; 149 } 150 151 @Nullable getDecorationInfo()152 public SectionDecorationInfo getDecorationInfo() { 153 return decorationInfo; 154 } 155 156 /** Sets the alpha of the decorator for this item. */ setDecorationFillAlpha(int alpha)157 protected void setDecorationFillAlpha(int alpha) { 158 if (decorationInfo == null || decorationInfo.getDecorationHandler() == null) { 159 return; 160 } 161 decorationInfo.getDecorationHandler().setFillAlpha(alpha); 162 } 163 } 164 165 protected final T mActivityContext; 166 protected final AlphabeticalAppsList<T> mApps; 167 // The text to show when there are no search results and no market search handler. 168 protected int mAppsPerRow; 169 170 protected final LayoutInflater mLayoutInflater; 171 protected final OnClickListener mOnIconClickListener; 172 protected final OnLongClickListener mOnIconLongClickListener; 173 protected OnFocusChangeListener mIconFocusListener; 174 BaseAllAppsAdapter(T activityContext, LayoutInflater inflater, AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider)175 public BaseAllAppsAdapter(T activityContext, LayoutInflater inflater, 176 AlphabeticalAppsList<T> apps, SearchAdapterProvider<?> adapterProvider) { 177 mActivityContext = activityContext; 178 mApps = apps; 179 mLayoutInflater = inflater; 180 181 mOnIconClickListener = mActivityContext.getItemOnClickListener(); 182 mOnIconLongClickListener = mActivityContext.getAllAppsItemLongClickListener(); 183 184 mAdapterProvider = adapterProvider; 185 } 186 187 /** Checks if the passed viewType represents all apps divider. */ isDividerViewType(int viewType)188 public static boolean isDividerViewType(int viewType) { 189 return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER); 190 } 191 192 /** Checks if the passed viewType represents all apps icon. */ isIconViewType(int viewType)193 public static boolean isIconViewType(int viewType) { 194 return isViewType(viewType, VIEW_TYPE_MASK_ICON); 195 } 196 197 /** Checks if the passed viewType represents private space header. */ isPrivateSpaceHeaderView(int viewType)198 public static boolean isPrivateSpaceHeaderView(int viewType) { 199 return isViewType(viewType, VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER); 200 } 201 202 /** Checks if the passed viewType represents private space system apps divider. */ isPrivateSpaceSysAppsDividerView(int viewType)203 public static boolean isPrivateSpaceSysAppsDividerView(int viewType) { 204 return isViewType(viewType, VIEW_TYPE_MASK_PRIVATE_SPACE_SYS_APPS_DIVIDER); 205 } 206 setIconFocusListener(OnFocusChangeListener focusListener)207 public void setIconFocusListener(OnFocusChangeListener focusListener) { 208 mIconFocusListener = focusListener; 209 } 210 211 /** 212 * Returns the layout manager. 213 */ getLayoutManager()214 public abstract RecyclerView.LayoutManager getLayoutManager(); 215 216 @Override onCreateViewHolder(ViewGroup parent, int viewType)217 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 218 switch (viewType) { 219 case VIEW_TYPE_ICON: 220 int layout = mActivityContext.getDeviceProfile().inv.enableTwoLinesInAllApps 221 ? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon; 222 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate( 223 layout, parent, false); 224 icon.setLongPressTimeoutFactor(1f); 225 icon.setOnFocusChangeListener(mIconFocusListener); 226 icon.setOnClickListener(mOnIconClickListener); 227 icon.setOnLongClickListener(mOnIconLongClickListener); 228 // Ensure the all apps icon height matches the workspace icons in portrait mode. 229 icon.getLayoutParams().height = 230 mActivityContext.getDeviceProfile().allAppsCellHeightPx; 231 return new ViewHolder(icon); 232 case VIEW_TYPE_EMPTY_SEARCH: 233 return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search, 234 parent, false)); 235 case VIEW_TYPE_ALL_APPS_DIVIDER, VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER: 236 return new ViewHolder(mLayoutInflater.inflate( 237 R.layout.private_space_divider, parent, false)); 238 case VIEW_TYPE_WORK_EDU_CARD: 239 return new ViewHolder(mLayoutInflater.inflate( 240 R.layout.work_apps_edu, parent, false)); 241 case VIEW_TYPE_WORK_DISABLED_CARD: 242 return new ViewHolder(mLayoutInflater.inflate( 243 R.layout.work_apps_paused, parent, false)); 244 case VIEW_TYPE_PRIVATE_SPACE_HEADER: 245 return new ViewHolder(mLayoutInflater.inflate( 246 R.layout.private_space_header, parent, false)); 247 case VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO: 248 return new ViewHolder(new View(mActivityContext)); 249 default: 250 if (mAdapterProvider.isViewSupported(viewType)) { 251 return mAdapterProvider.onCreateViewHolder(mLayoutInflater, parent, viewType); 252 } 253 throw new RuntimeException("Unexpected view type" + viewType); 254 } 255 } 256 257 @Override onBindViewHolder(ViewHolder holder, int position)258 public void onBindViewHolder(ViewHolder holder, int position) { 259 holder.itemView.setVisibility(View.VISIBLE); 260 switch (holder.getItemViewType()) { 261 case VIEW_TYPE_ICON: { 262 AdapterItem adapterItem = mApps.getAdapterItems().get(position); 263 BubbleTextView icon = (BubbleTextView) holder.itemView; 264 icon.reset(); 265 icon.applyFromApplicationInfo(adapterItem.itemInfo); 266 icon.setOnFocusChangeListener(mIconFocusListener); 267 PrivateProfileManager privateProfileManager = mApps.getPrivateProfileManager(); 268 if (privateProfileManager != null) { 269 // Set the alpha of the private space icon to 0 upon expanding the header so the 270 // alpha can animate -> 1. This should only be in effect when doing a 271 // transitioning between Locked/Unlocked state. 272 boolean isPrivateSpaceItem = 273 privateProfileManager.isPrivateSpaceItem(adapterItem); 274 if (icon.getAlpha() == 0 || icon.getAlpha() == 1) { 275 icon.setAlpha(isPrivateSpaceItem 276 && privateProfileManager.isStateTransitioning() 277 && (privateProfileManager.isScrolling() || 278 privateProfileManager.getReadyToAnimate()) 279 && privateProfileManager.getCurrentState() == STATE_ENABLED 280 ? 0 : 1); 281 Log.d(TAG, "onBindViewHolder: " 282 + "isPrivateSpaceItem: " + isPrivateSpaceItem 283 + " isStateTransitioning: " + privateProfileManager.isStateTransitioning() 284 + " isScrolling: " + privateProfileManager.isScrolling() 285 + " readyToAnimate: " + privateProfileManager.getReadyToAnimate() 286 + " currentState: " + privateProfileManager.getCurrentState() 287 + " currentAlpha: " + icon.getAlpha()); 288 } 289 // Views can still be bounded before the app list is updated hence showing icons 290 // after collapsing. 291 if (privateProfileManager.getCurrentState() == STATE_DISABLED 292 && isPrivateSpaceItem) { 293 adapterItem.decorationInfo = null; 294 icon.setVisibility(GONE); 295 } 296 } 297 break; 298 } 299 case VIEW_TYPE_EMPTY_SEARCH: { 300 AppInfo info = mApps.getAdapterItems().get(position).itemInfo; 301 if (info != null) { 302 ((TextView) holder.itemView).setText(mActivityContext.getString( 303 R.string.all_apps_no_search_results, info.title)); 304 } 305 break; 306 } 307 case VIEW_TYPE_PRIVATE_SPACE_HEADER: 308 RelativeLayout psHeaderLayout = holder.itemView.findViewById( 309 R.id.ps_header_layout); 310 mApps.getPrivateProfileManager().bindPrivateSpaceHeaderViewElements(psHeaderLayout); 311 AdapterItem adapterItem = mApps.getAdapterItems().get(position); 312 int roundRegions = ROUND_TOP_LEFT | ROUND_TOP_RIGHT; 313 if (mApps.getPrivateProfileManager().getCurrentState() == STATE_DISABLED) { 314 roundRegions |= (ROUND_BOTTOM_LEFT | ROUND_BOTTOM_RIGHT); 315 } 316 adapterItem.decorationInfo = 317 new SectionDecorationInfo(mActivityContext, roundRegions, 318 false /* decorateTogether */); 319 break; 320 case VIEW_TYPE_PRIVATE_SPACE_SYS_APPS_DIVIDER: 321 adapterItem = mApps.getAdapterItems().get(position); 322 adapterItem.decorationInfo = mApps.getPrivateProfileManager().getCurrentState() 323 == STATE_DISABLED ? null : new SectionDecorationInfo(mActivityContext, 324 ROUND_NOTHING, true /* decorateTogether */); 325 break; 326 case VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO: 327 case VIEW_TYPE_ALL_APPS_DIVIDER: 328 case VIEW_TYPE_WORK_DISABLED_CARD: 329 // nothing to do 330 break; 331 case VIEW_TYPE_WORK_EDU_CARD: 332 ((WorkEduCard) holder.itemView).setPosition(position); 333 break; 334 default: 335 if (mAdapterProvider.isViewSupported(holder.getItemViewType())) { 336 mAdapterProvider.onBindView(holder, position); 337 } 338 } 339 } 340 341 @Override onFailedToRecycleView(ViewHolder holder)342 public boolean onFailedToRecycleView(ViewHolder holder) { 343 // Always recycle and we will reset the view when it is bound 344 return true; 345 } 346 347 @Override getItemCount()348 public int getItemCount() { 349 return mApps.getAdapterItems().size(); 350 } 351 352 @Override getItemViewType(int position)353 public int getItemViewType(int position) { 354 AdapterItem item = mApps.getAdapterItems().get(position); 355 return item.viewType; 356 } 357 isViewType(int viewType, int viewTypeMask)358 protected static boolean isViewType(int viewType, int viewTypeMask) { 359 return (viewType & viewTypeMask) != 0; 360 } 361 362 } 363