• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.secondarydisplay;
17 
18 import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
19 import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.content.Intent;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.os.Bundle;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.ViewAnimationUtils;
30 import android.view.inputmethod.InputMethodManager;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.UiThread;
34 
35 import com.android.launcher3.AbstractFloatingView;
36 import com.android.launcher3.BaseActivity;
37 import com.android.launcher3.BubbleTextView;
38 import com.android.launcher3.DragSource;
39 import com.android.launcher3.DropTarget;
40 import com.android.launcher3.InvariantDeviceProfile;
41 import com.android.launcher3.LauncherAppState;
42 import com.android.launcher3.LauncherModel;
43 import com.android.launcher3.LauncherSettings;
44 import com.android.launcher3.R;
45 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
46 import com.android.launcher3.allapps.AllAppsStore;
47 import com.android.launcher3.dragndrop.DragController;
48 import com.android.launcher3.dragndrop.DragOptions;
49 import com.android.launcher3.dragndrop.DraggableView;
50 import com.android.launcher3.graphics.DragPreviewProvider;
51 import com.android.launcher3.icons.FastBitmapDrawable;
52 import com.android.launcher3.model.BgDataModel;
53 import com.android.launcher3.model.StringCache;
54 import com.android.launcher3.model.data.AppInfo;
55 import com.android.launcher3.model.data.ItemInfo;
56 import com.android.launcher3.model.data.ItemInfoWithIcon;
57 import com.android.launcher3.popup.PopupContainerWithArrow;
58 import com.android.launcher3.popup.PopupDataProvider;
59 import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
60 import com.android.launcher3.util.ComponentKey;
61 import com.android.launcher3.util.PackageUserKey;
62 import com.android.launcher3.util.Preconditions;
63 import com.android.launcher3.util.Themes;
64 import com.android.launcher3.views.BaseDragLayer;
65 
66 import java.util.HashMap;
67 import java.util.Map;
68 
69 /**
70  * Launcher activity for secondary displays
71  */
72 public class SecondaryDisplayLauncher extends BaseActivity
73         implements BgDataModel.Callbacks, DragController.DragListener {
74 
75     private LauncherModel mModel;
76     private SecondaryDragLayer mDragLayer;
77     private SecondaryDragController mDragController;
78     private ActivityAllAppsContainerView<SecondaryDisplayLauncher> mAppsView;
79     private View mAppsButton;
80 
81     private PopupDataProvider mPopupDataProvider;
82 
83     private boolean mAppDrawerShown = false;
84 
85     private StringCache mStringCache;
86     private SecondaryDisplayPredictions mSecondaryDisplayPredictions;
87 
88     private final int[] mTempXY = new int[2];
89 
90     @Override
onCreate(Bundle savedInstanceState)91     protected void onCreate(Bundle savedInstanceState) {
92         super.onCreate(savedInstanceState);
93         setWallpaperDependentTheme(this);
94         mModel = LauncherAppState.getInstance(this).getModel();
95         mDragController = new SecondaryDragController(this);
96         mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
97 
98         mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(this)
99                 .createDeviceProfileForSecondaryDisplay(this);
100         mDeviceProfile.autoResizeAllAppsCells();
101 
102         setContentView(R.layout.secondary_launcher);
103         mDragLayer = findViewById(R.id.drag_layer);
104         mAppsView = findViewById(R.id.apps_view);
105         mAppsButton = findViewById(R.id.all_apps_button);
106         // TODO (b/391965805): Replace this flag with DesktopExperiences flag.
107         if (enableTaskbarConnectedDisplays()) {
108             mAppsButton.setVisibility(View.INVISIBLE);
109         }
110 
111         mDragController.addDragListener(this);
112         mPopupDataProvider = new PopupDataProvider(this);
113 
114         mModel.addCallbacksAndLoad(this);
115     }
116 
117     @Override
onPause()118     protected void onPause() {
119         super.onPause();
120         mDragController.cancelDrag();
121     }
122 
123     @Override
onNewIntent(Intent intent)124     public void onNewIntent(Intent intent) {
125         super.onNewIntent(intent);
126 
127         if (Intent.ACTION_MAIN.equals(intent.getAction())) {
128             // Hide keyboard.
129             final View v = getWindow().peekDecorView();
130             if (v != null && v.getWindowToken() != null) {
131                 getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
132                         v.getWindowToken(), 0);
133             }
134         }
135 
136         // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
137         showAppDrawer(false);
138     }
139 
getDragController()140     public DragController getDragController() {
141         return mDragController;
142     }
143 
144     @Override
onBackPressed()145     public void onBackPressed() {
146         if (finishAutoCancelActionMode()) {
147             return;
148         }
149 
150         if (mDragController.isDragging()) {
151             mDragController.cancelDrag();
152             return;
153         }
154 
155         // Note: There should be at most one log per method call. This is enforced implicitly
156         // by using if-else statements.
157         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
158         if (topView != null && topView.canHandleBack()) {
159             // Handled by the floating view.
160             topView.onBackInvoked();
161         } else {
162             showAppDrawer(false);
163         }
164     }
165 
166     @Override
onDestroy()167     protected void onDestroy() {
168         super.onDestroy();
169         mModel.removeCallbacks(this);
170     }
171 
isAppDrawerShown()172     public boolean isAppDrawerShown() {
173         return mAppDrawerShown;
174     }
175 
176     @Override
getAppsView()177     public ActivityAllAppsContainerView<SecondaryDisplayLauncher> getAppsView() {
178         return mAppsView;
179     }
180 
181     @Override
getDragLayer()182     public BaseDragLayer getDragLayer() {
183         return mDragLayer;
184     }
185 
186     @Override
bindIncrementalDownloadProgressUpdated(AppInfo app)187     public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
188         mAppsView.getAppsStore().updateProgressBar(app);
189     }
190 
191     /**
192      * Called when apps-button is clicked
193      */
onAppsButtonClicked(View v)194     public void onAppsButtonClicked(View v) {
195         showAppDrawer(true);
196     }
197 
198     /**
199      * Show/hide app drawer card with animation.
200      */
showAppDrawer(boolean show)201     public void showAppDrawer(boolean show) {
202         if (show == mAppDrawerShown) {
203             return;
204         }
205 
206         float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight());
207         float closeR = Themes.getDialogCornerRadius(this);
208         float startR = mAppsButton.getWidth() / 2f;
209 
210         float[] buttonPos = new float[]{startR, startR};
211         mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
212         mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
213         final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
214                 (int) buttonPos[0], (int) buttonPos[1],
215                 show ? closeR : openR, show ? openR : closeR);
216 
217         if (show) {
218             mAppDrawerShown = true;
219             mAppsView.setVisibility(View.VISIBLE);
220             mAppsButton.setVisibility(View.INVISIBLE);
221             mSecondaryDisplayPredictions.updateAppDivider();
222         } else {
223             mAppDrawerShown = false;
224             animator.addListener(new AnimatorListenerAdapter() {
225                 @Override
226                 public void onAnimationEnd(Animator animation) {
227                     mAppsView.setVisibility(View.INVISIBLE);
228                     // TODO (b/391965805): Replace this flag with DesktopExperiences flag.
229                     mAppsButton.setVisibility(
230                             enableTaskbarConnectedDisplays() ? View.INVISIBLE : View.VISIBLE);
231                     mAppsView.getSearchUiManager().resetSearch();
232                 }
233             });
234         }
235         animator.start();
236     }
237 
238     @Override
startBinding()239     public void startBinding() {
240         mDragController.cancelDrag();
241     }
242 
243     @Override
bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)244     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
245         mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
246     }
247 
248     @UiThread
249     @Override
bindAllApplications(AppInfo[] apps, int flags, Map<PackageUserKey, Integer> packageUserKeytoUidMap)250     public void bindAllApplications(AppInfo[] apps, int flags,
251             Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
252         Preconditions.assertUIThread();
253         AllAppsStore<SecondaryDisplayLauncher> appsStore = mAppsView.getAppsStore();
254         appsStore.setApps(apps, flags, packageUserKeytoUidMap);
255         PopupContainerWithArrow.dismissInvalidPopup(this);
256     }
257 
258     @Override
bindExtraContainerItems(BgDataModel.FixedContainerItems item)259     public void bindExtraContainerItems(BgDataModel.FixedContainerItems item) {
260         if (item.containerId == LauncherSettings.Favorites.CONTAINER_PREDICTION) {
261             mSecondaryDisplayPredictions.setPredictedApps(item);
262         }
263     }
264 
265     @Override
getStringCache()266     public StringCache getStringCache() {
267         return mStringCache;
268     }
269 
270     @Override
bindStringCache(StringCache cache)271     public void bindStringCache(StringCache cache) {
272         mStringCache = cache;
273     }
274 
275     @Override
276     @NonNull
getPopupDataProvider()277     public PopupDataProvider getPopupDataProvider() {
278         return mPopupDataProvider;
279     }
280 
281     @Override
getItemOnClickListener()282     public OnClickListener getItemOnClickListener() {
283         return this::onIconClicked;
284     }
285 
286     @Override
getAllAppsItemLongClickListener()287     public View.OnLongClickListener getAllAppsItemLongClickListener() {
288         return v -> mDragLayer.onIconLongClicked(v);
289     }
290 
onIconClicked(View v)291     private void onIconClicked(View v) {
292         // Make sure that rogue clicks don't get through while allapps is launching, or after the
293         // view has detached (it's possible for this to happen if the view is removed mid touch).
294         if (v.getWindowToken() == null) return;
295 
296         Object tag = v.getTag();
297         if (tag instanceof ItemClickProxy) {
298             ((ItemClickProxy) tag).onItemClicked(v);
299         } else if (tag instanceof ItemInfo) {
300             ItemInfo item = (ItemInfo) tag;
301             Intent intent;
302             if (item instanceof ItemInfoWithIcon
303                     && (((ItemInfoWithIcon) item).runtimeStatusFlags
304                     & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
305                 ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
306                 intent = appInfo.getMarketIntent(this);
307             } else {
308                 intent = item.getIntent();
309             }
310             if (intent == null) {
311                 throw new IllegalArgumentException("Input must have a valid intent");
312             }
313             startActivitySafely(v, intent, item);
314         }
315     }
316 
317     /**
318      * Core functionality for beginning a drag operation for an item that will be dropped within
319      * the secondary display grid home screen
320      */
beginDragShared(View child, DragSource source, DragOptions options)321     public void beginDragShared(View child, DragSource source, DragOptions options) {
322         Object dragObject = child.getTag();
323         if (!(dragObject instanceof ItemInfo)) {
324             String msg = "Drag started with a view that has no tag set. This "
325                     + "will cause a crash (issue 11627249) down the line. "
326                     + "View: " + child + "  tag: " + child.getTag();
327             throw new IllegalStateException(msg);
328         }
329         beginDragShared(child, source, (ItemInfo) dragObject,
330                 new DragPreviewProvider(child), options);
331     }
332 
beginDragShared(View child, DragSource source, ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions options)333     private void beginDragShared(View child, DragSource source,
334             ItemInfo dragObject, DragPreviewProvider previewProvider, DragOptions options) {
335 
336         float iconScale = 1f;
337         if (child instanceof BubbleTextView) {
338             FastBitmapDrawable icon = ((BubbleTextView) child).getIcon();
339             if (icon != null) {
340                 iconScale = icon.getAnimatedScale();
341             }
342         }
343 
344         // clear pressed state if necessary
345         child.clearFocus();
346         child.setPressed(false);
347         if (child instanceof BubbleTextView) {
348             BubbleTextView icon = (BubbleTextView) child;
349             icon.clearPressedBackground();
350         }
351 
352         DraggableView draggableView = null;
353         if (child instanceof DraggableView) {
354             draggableView = (DraggableView) child;
355         }
356 
357         final View contentView = previewProvider.getContentView();
358         final float scale;
359         // The draggable drawable follows the touch point around on the screen
360         final Drawable drawable;
361         if (contentView == null) {
362             drawable = previewProvider.createDrawable();
363             scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
364         } else {
365             drawable = null;
366             scale = previewProvider.getScaleAndPosition(contentView, mTempXY);
367         }
368 
369         int dragLayerX = mTempXY[0];
370         int dragLayerY = mTempXY[1];
371 
372         Rect dragRect = new Rect();
373         if (draggableView != null) {
374             draggableView.getSourceVisualDragBounds(dragRect);
375             dragLayerY += dragRect.top;
376         }
377 
378         if (options.preDragCondition != null) {
379             int xOffSet = options.preDragCondition.getDragOffset().x;
380             int yOffSet = options.preDragCondition.getDragOffset().y;
381             if (xOffSet != 0 && yOffSet != 0) {
382                 dragLayerX += xOffSet;
383                 dragLayerY += yOffSet;
384             }
385         }
386 
387         if (contentView != null) {
388             mDragController.startDrag(
389                     contentView,
390                     draggableView,
391                     dragLayerX,
392                     dragLayerY,
393                     source,
394                     dragObject,
395                     dragRect,
396                     scale * iconScale,
397                     scale,
398                     options);
399         } else {
400             mDragController.startDrag(
401                     drawable,
402                     draggableView,
403                     dragLayerX,
404                     dragLayerY,
405                     source,
406                     dragObject,
407                     dragRect,
408                     scale * iconScale,
409                     scale,
410                     options);
411         }
412     }
413 
414     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)415     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { }
416 
417     @Override
onDragEnd()418     public void onDragEnd() { }
419 }
420