• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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