• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 
17 package com.android.launcher3.appprediction;
18 
19 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
20 
21 import android.content.Context;
22 import android.graphics.Canvas;
23 import android.os.Build;
24 import android.util.AttributeSet;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.view.accessibility.AccessibilityNodeInfo;
29 import android.widget.LinearLayout;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 
34 import com.android.launcher3.BubbleTextView;
35 import com.android.launcher3.DeviceProfile;
36 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
37 import com.android.launcher3.Flags;
38 import com.android.launcher3.R;
39 import com.android.launcher3.Utilities;
40 import com.android.launcher3.allapps.FloatingHeaderRow;
41 import com.android.launcher3.allapps.FloatingHeaderView;
42 import com.android.launcher3.anim.AlphaUpdateListener;
43 import com.android.launcher3.keyboard.FocusIndicatorHelper;
44 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
45 import com.android.launcher3.model.data.ItemInfo;
46 import com.android.launcher3.model.data.ItemInfoWithIcon;
47 import com.android.launcher3.model.data.WorkspaceItemInfo;
48 import com.android.launcher3.views.ActivityContext;
49 
50 import java.io.PrintWriter;
51 import java.util.ArrayList;
52 import java.util.List;
53 import java.util.stream.Collectors;
54 
55 public class PredictionRowView<T extends Context & ActivityContext>
56         extends LinearLayout implements OnDeviceProfileChangeListener, FloatingHeaderRow {
57 
58     private final T mActivityContext;
59     private int mNumPredictedAppsPerRow;
60     // Vertical padding of the icon that contributes to the expected cell height.
61     private final int mVerticalPadding;
62     // Extra padding that is used in the top app rows (prediction and search) that is not used in
63     // the regular A-Z list.
64     private final int mTopRowExtraHeight;
65 
66     // Helper to drawing the focus indicator.
67     private final FocusIndicatorHelper mFocusHelper;
68 
69     // The set of predicted apps resolved from the component names and the current set of apps
70     private final List<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
71 
72     private FloatingHeaderView mParent;
73 
74     private boolean mPredictionsEnabled = false;
75 
76     private boolean mPredictionUiUpdatePaused = false;
77 
PredictionRowView(@onNull Context context)78     public PredictionRowView(@NonNull Context context) {
79         this(context, null);
80     }
81 
PredictionRowView(@onNull Context context, @Nullable AttributeSet attrs)82     public PredictionRowView(@NonNull Context context, @Nullable AttributeSet attrs) {
83         super(context, attrs);
84         setOrientation(LinearLayout.HORIZONTAL);
85 
86         mFocusHelper = new SimpleFocusIndicatorHelper(this);
87         mActivityContext = ActivityContext.lookupContext(context);
88         mNumPredictedAppsPerRow = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
89         mTopRowExtraHeight = getResources().getDimensionPixelSize(
90                 R.dimen.all_apps_search_top_row_extra_height);
91         mVerticalPadding = getResources().getDimensionPixelSize(
92                 R.dimen.all_apps_predicted_icon_vertical_padding);
93         updateVisibility();
94     }
95 
96     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)97     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
98         super.onInitializeAccessibilityNodeInfo(info);
99         if (Build.VERSION.SDK_INT >= UPSIDE_DOWN_CAKE) {
100             info.setContainerTitle(mActivityContext.getString(R.string.title_app_suggestions));
101         }
102     }
103 
104     @Override
onAttachedToWindow()105     protected void onAttachedToWindow() {
106         super.onAttachedToWindow();
107         mActivityContext.addOnDeviceProfileChangeListener(this);
108     }
109 
110     @Override
onDetachedFromWindow()111     protected void onDetachedFromWindow() {
112         super.onDetachedFromWindow();
113         mActivityContext.removeOnDeviceProfileChangeListener(this);
114     }
115 
setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden)116     public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
117         mParent = parent;
118     }
119 
updateVisibility()120     private void updateVisibility() {
121         setVisibility(mPredictionsEnabled ? VISIBLE : GONE);
122         if (mActivityContext.getAppsView() != null) {
123             if (mPredictionsEnabled) {
124                 mActivityContext.getAppsView().getAppsStore().registerIconContainer(this);
125             } else {
126                 mActivityContext.getAppsView().getAppsStore().unregisterIconContainer(this);
127             }
128         }
129     }
130 
131     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)132     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
133         super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(getExpectedHeight(),
134                 MeasureSpec.EXACTLY));
135     }
136 
137     @Override
dispatchDraw(Canvas canvas)138     protected void dispatchDraw(Canvas canvas) {
139         mFocusHelper.draw(canvas);
140         super.dispatchDraw(canvas);
141     }
142 
143     @Override
getExpectedHeight()144     public int getExpectedHeight() {
145         DeviceProfile deviceProfile = mActivityContext.getDeviceProfile();
146         int iconHeight = deviceProfile.allAppsIconSizePx;
147         int iconPadding = deviceProfile.allAppsIconDrawablePaddingPx;
148         int textHeight = Utilities.calculateTextHeight(deviceProfile.allAppsIconTextSizePx);
149         int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2;
150         // Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line
151         // is not enabled. Otherwise, the extra height will increase by just the textHeight.
152         int extraHeight = deviceProfile.inv.enableTwoLinesInAllApps
153                 ? (textHeight + mTopRowExtraHeight) : mTopRowExtraHeight;
154         totalHeight += extraHeight;
155         return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
156     }
157 
158     @Override
shouldDraw()159     public boolean shouldDraw() {
160         return getVisibility() != GONE;
161     }
162 
163     @Override
hasVisibleContent()164     public boolean hasVisibleContent() {
165         return mPredictionsEnabled;
166     }
167 
168     @Override
isVisible()169     public boolean isVisible() {
170         return getVisibility() == VISIBLE;
171     }
172 
173     /**
174      * Returns the predicted apps.
175      */
getPredictedApps()176     public List<ItemInfoWithIcon> getPredictedApps() {
177         return new ArrayList<>(mPredictedApps);
178     }
179 
180     /**
181      * Sets the current set of predicted apps.
182      *
183      * This can be called before we get the full set of applications, we should merge the results
184      * only in onPredictionsUpdated() which is idempotent.
185      *
186      * If the number of predicted apps is the same as the previous list of predicted apps,
187      * we can optimize by swapping them in place.
188      */
setPredictedApps(List<ItemInfo> items)189     public void setPredictedApps(List<ItemInfo> items) {
190         applyPredictedApps(items);
191     }
192 
applyPredictedApps(List<ItemInfo> items)193     private void applyPredictedApps(List<ItemInfo> items) {
194         mPredictedApps.clear();
195         mPredictedApps.addAll(items.stream()
196                 .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
197                 .map(itemInfo -> (WorkspaceItemInfo) itemInfo).collect(Collectors.toList()));
198         applyPredictionApps();
199     }
200 
201     @Override
onDeviceProfileChanged(DeviceProfile dp)202     public void onDeviceProfileChanged(DeviceProfile dp) {
203         mNumPredictedAppsPerRow = dp.numShownAllAppsColumns;
204         removeAllViews();
205         applyPredictionApps();
206     }
207 
208     /** Pause the prediction row UI update */
setPredictionUiUpdatePaused(boolean predictionUiUpdatePaused)209     public void setPredictionUiUpdatePaused(boolean predictionUiUpdatePaused) {
210         mPredictionUiUpdatePaused = predictionUiUpdatePaused;
211         if (!mPredictionUiUpdatePaused) {
212             applyPredictionApps();
213         }
214     }
215 
applyPredictionApps()216     private void applyPredictionApps() {
217         if (mPredictionUiUpdatePaused) {
218             return;
219         }
220         if (getChildCount() != mNumPredictedAppsPerRow) {
221             while (getChildCount() > mNumPredictedAppsPerRow) {
222                 removeViewAt(0);
223             }
224             LayoutInflater inflater = mActivityContext.getAppsView().getLayoutInflater();
225             while (getChildCount() < mNumPredictedAppsPerRow) {
226                 BubbleTextView icon = (BubbleTextView) inflater.inflate(
227                         R.layout.all_apps_prediction_row_icon, this, false);
228                 icon.setOnClickListener(mActivityContext.getItemOnClickListener());
229                 icon.setOnLongClickListener(mActivityContext.getAllAppsItemLongClickListener());
230                 icon.setLongPressTimeoutFactor(1f);
231                 icon.setOnFocusChangeListener(mFocusHelper);
232 
233                 LayoutParams lp = (LayoutParams) icon.getLayoutParams();
234                 if (Flags.enableFocusOutline()) {
235                     lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
236                 } else {
237                     // Ensure the all apps icon height matches the workspace icons in portrait mode.
238                     lp.height = mActivityContext.getDeviceProfile().allAppsCellHeightPx;
239                 }
240                 lp.width = 0;
241                 lp.weight = 1;
242                 addView(icon);
243             }
244         }
245 
246         int predictionCount = mPredictedApps.size();
247 
248         for (int i = 0; i < getChildCount(); i++) {
249             BubbleTextView icon = (BubbleTextView) getChildAt(i);
250             icon.reset();
251             if (predictionCount > i) {
252                 icon.setVisibility(View.VISIBLE);
253                 WorkspaceItemInfo predictedItem = mPredictedApps.get(i);
254                 predictedItem.rank = i;
255                 predictedItem.cellX = i;
256                 predictedItem.cellY = 0;
257                 icon.applyFromWorkspaceItem(predictedItem);
258             } else {
259                 icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
260             }
261         }
262 
263         boolean predictionsEnabled = predictionCount > 0;
264         if (predictionsEnabled != mPredictionsEnabled) {
265             mPredictionsEnabled = predictionsEnabled;
266             updateVisibility();
267         }
268         mParent.onHeightUpdated();
269     }
270 
271     @Override
hasOverlappingRendering()272     public boolean hasOverlappingRendering() {
273         return false;
274     }
275 
276 
277     @Override
setVerticalScroll(int scroll, boolean isScrolledOut)278     public void setVerticalScroll(int scroll, boolean isScrolledOut) {
279         if (!isScrolledOut) {
280             setTranslationY(scroll);
281         }
282         setAlpha(isScrolledOut ? 0 : 1);
283         if (getVisibility() != GONE) {
284             AlphaUpdateListener.updateVisibility(this);
285         }
286     }
287 
288     @Override
getTypeClass()289     public Class<PredictionRowView> getTypeClass() {
290         return PredictionRowView.class;
291     }
292 
293     @Override
getFocusedChild()294     public View getFocusedChild() {
295         return getChildAt(0);
296     }
297 
dump(String prefix, PrintWriter writer)298     public void dump(String prefix, PrintWriter writer) {
299         writer.println(prefix + "PredictionRowView");
300         writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled);
301         writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused);
302         writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow);
303         writer.println(prefix + "\tmPredictedApps: " + mPredictedApps.size());
304         for (WorkspaceItemInfo info : mPredictedApps) {
305             writer.println(prefix + "\t\t" + info);
306         }
307     }
308 }
309