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