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