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.model.AppLaunchTracker.CONTAINER_ALL_APPS;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.app.ActivityOptions;
23 import android.content.Intent;
24 import android.os.Bundle;
25 import android.view.View;
26 import android.view.View.OnClickListener;
27 import android.view.ViewAnimationUtils;
28 import android.view.inputmethod.InputMethodManager;
29 
30 import com.android.launcher3.AbstractFloatingView;
31 import com.android.launcher3.BaseDraggingActivity;
32 import com.android.launcher3.InvariantDeviceProfile;
33 import com.android.launcher3.LauncherAppState;
34 import com.android.launcher3.LauncherModel;
35 import com.android.launcher3.R;
36 import com.android.launcher3.allapps.AllAppsContainerView;
37 import com.android.launcher3.model.BgDataModel;
38 import com.android.launcher3.model.data.AppInfo;
39 import com.android.launcher3.model.data.ItemInfo;
40 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
41 import com.android.launcher3.model.data.PromiseAppInfo;
42 import com.android.launcher3.model.data.WorkspaceItemInfo;
43 import com.android.launcher3.popup.PopupDataProvider;
44 import com.android.launcher3.util.ComponentKey;
45 import com.android.launcher3.util.IntArray;
46 import com.android.launcher3.util.ItemInfoMatcher;
47 import com.android.launcher3.util.Themes;
48 import com.android.launcher3.util.ViewOnDrawExecutor;
49 import com.android.launcher3.views.BaseDragLayer;
50 import com.android.launcher3.widget.WidgetListRowEntry;
51 
52 import java.util.ArrayList;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.List;
56 
57 /**
58  * Launcher activity for secondary displays
59  */
60 public class SecondaryDisplayLauncher extends BaseDraggingActivity
61         implements BgDataModel.Callbacks {
62 
63     private LauncherModel mModel;
64 
65     private BaseDragLayer mDragLayer;
66     private AllAppsContainerView mAppsView;
67     private View mAppsButton;
68 
69     private PopupDataProvider mPopupDataProvider;
70 
71     private boolean mAppDrawerShown = false;
72 
73     @Override
onCreate(Bundle savedInstanceState)74     protected void onCreate(Bundle savedInstanceState) {
75         super.onCreate(savedInstanceState);
76         mModel = LauncherAppState.getInstance(this).getModel();
77         if (getWindow().getDecorView().isAttachedToWindow()) {
78             initUi();
79         }
80     }
81 
82     @Override
onAttachedToWindow()83     public void onAttachedToWindow() {
84         super.onAttachedToWindow();
85         initUi();
86     }
87 
initUi()88     private void initUi() {
89         if (mDragLayer != null) {
90             return;
91         }
92         InvariantDeviceProfile currentDisplayIdp =
93                 new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
94 
95         // Disable transpose layout and use multi-window mode so that the icons are scaled properly
96         mDeviceProfile = currentDisplayIdp.getDeviceProfile(this)
97                 .toBuilder(this)
98                 .setMultiWindowMode(true)
99                 .setTransposeLayoutWithOrientation(false)
100                 .build();
101         mDeviceProfile.autoResizeAllAppsCells();
102 
103         setContentView(R.layout.secondary_launcher);
104         mDragLayer = findViewById(R.id.drag_layer);
105         mAppsView = findViewById(R.id.apps_view);
106         mAppsButton = findViewById(R.id.all_apps_button);
107 
108         mPopupDataProvider = new PopupDataProvider(
109                 mAppsView.getAppsStore()::updateNotificationDots);
110 
111         mModel.addCallbacksAndLoad(this);
112     }
113 
114     @Override
onNewIntent(Intent intent)115     public void onNewIntent(Intent intent) {
116         super.onNewIntent(intent);
117 
118         if (Intent.ACTION_MAIN.equals(intent.getAction())) {
119             // Hide keyboard.
120             final View v = getWindow().peekDecorView();
121             if (v != null && v.getWindowToken() != null) {
122                 getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
123                         v.getWindowToken(), 0);
124             }
125         }
126 
127         // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
128         showAppDrawer(false);
129     }
130 
131     @Override
onBackPressed()132     public void onBackPressed() {
133         if (finishAutoCancelActionMode()) {
134             return;
135         }
136 
137         // Note: There should be at most one log per method call. This is enforced implicitly
138         // by using if-else statements.
139         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
140         if (topView != null && topView.onBackPressed()) {
141             // Handled by the floating view.
142         } else {
143             showAppDrawer(false);
144         }
145     }
146 
147     @Override
onDestroy()148     protected void onDestroy() {
149         super.onDestroy();
150         mModel.removeCallbacks(this);
151     }
152 
isAppDrawerShown()153     public boolean isAppDrawerShown() {
154         return mAppDrawerShown;
155     }
156 
getAppsView()157     public AllAppsContainerView getAppsView() {
158         return mAppsView;
159     }
160 
161     @Override
getOverviewPanel()162     public <T extends View> T getOverviewPanel() {
163         return null;
164     }
165 
166     @Override
getRootView()167     public View getRootView() {
168         return mDragLayer;
169     }
170 
171     @Override
getActivityLaunchOptions(View v)172     public ActivityOptions getActivityLaunchOptions(View v) {
173         return null;
174     }
175 
176     @Override
reapplyUi()177     protected void reapplyUi() { }
178 
179     @Override
getDragLayer()180     public BaseDragLayer getDragLayer() {
181         return mDragLayer;
182     }
183 
184     @Override
getPageToBindSynchronously()185     public int getPageToBindSynchronously() {
186         return 0;
187     }
188 
189     @Override
clearPendingBinds()190     public void clearPendingBinds() { }
191 
192     @Override
startBinding()193     public void startBinding() { }
194 
195     @Override
bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons)196     public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
197 
198     @Override
bindPredictedItems(List<AppInfo> appInfos, IntArray ranks)199     public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
200 
201     @Override
bindScreens(IntArray orderedScreenIds)202     public void bindScreens(IntArray orderedScreenIds) { }
203 
204     @Override
finishFirstPageBind(ViewOnDrawExecutor executor)205     public void finishFirstPageBind(ViewOnDrawExecutor executor) {
206         if (executor != null) {
207             executor.onLoadAnimationCompleted();
208         }
209     }
210 
211     @Override
finishBindingItems(int pageBoundFirst)212     public void finishBindingItems(int pageBoundFirst) { }
213 
214     @Override
preAddApps()215     public void preAddApps() { }
216 
217     @Override
bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)218     public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
219             ArrayList<ItemInfo> addAnimated) { }
220 
221     @Override
bindPromiseAppProgressUpdated(PromiseAppInfo app)222     public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
223         mAppsView.getAppsStore().updatePromiseAppProgress(app);
224     }
225 
226     @Override
bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated)227     public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) { }
228 
229     @Override
bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)230     public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
231 
232     @Override
bindRestoreItemsChange(HashSet<ItemInfo> updates)233     public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
234 
235     @Override
bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher)236     public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
237 
238     @Override
bindAllWidgets(ArrayList<WidgetListRowEntry> widgets)239     public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets) { }
240 
241     @Override
onPageBoundSynchronously(int page)242     public void onPageBoundSynchronously(int page) { }
243 
244     @Override
executeOnNextDraw(ViewOnDrawExecutor executor)245     public void executeOnNextDraw(ViewOnDrawExecutor executor) {
246         executor.attachTo(getDragLayer(), false, null);
247     }
248 
249     /**
250      * Called when apps-button is clicked
251      */
onAppsButtonClicked(View v)252     public void onAppsButtonClicked(View v) {
253         showAppDrawer(true);
254     }
255 
256     /**
257      * Show/hide app drawer card with animation.
258      */
showAppDrawer(boolean show)259     public void showAppDrawer(boolean show) {
260         if (show == mAppDrawerShown) {
261             return;
262         }
263 
264         float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight());
265         float closeR = Themes.getDialogCornerRadius(this);
266         float startR = mAppsButton.getWidth() / 2f;
267 
268         float[] buttonPos = new float[] { startR, startR};
269         mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
270         mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
271         final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
272                 (int) buttonPos[0], (int) buttonPos[1],
273                 show ? closeR : openR, show ? openR : closeR);
274 
275         if (show) {
276             mAppDrawerShown = true;
277             mAppsView.setVisibility(View.VISIBLE);
278             mAppsButton.setVisibility(View.INVISIBLE);
279         } else {
280             mAppDrawerShown = false;
281             animator.addListener(new AnimatorListenerAdapter() {
282                 @Override
283                 public void onAnimationEnd(Animator animation) {
284                     mAppsView.setVisibility(View.INVISIBLE);
285                     mAppsButton.setVisibility(View.VISIBLE);
286                     mAppsView.getSearchUiManager().resetSearch();
287                 }
288             });
289         }
290         animator.start();
291     }
292 
293     @Override
bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)294     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
295         mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
296     }
297 
298     @Override
bindAllApplications(AppInfo[] apps, int flags)299     public void bindAllApplications(AppInfo[] apps, int flags) {
300         mAppsView.getAppsStore().setApps(apps, flags);
301     }
302 
getPopupDataProvider()303     public PopupDataProvider getPopupDataProvider() {
304         return mPopupDataProvider;
305     }
306 
307     @Override
getItemOnClickListener()308     public OnClickListener getItemOnClickListener() {
309         return this::onIconClicked;
310     }
311 
onIconClicked(View v)312     private void onIconClicked(View v) {
313         // Make sure that rogue clicks don't get through while allapps is launching, or after the
314         // view has detached (it's possible for this to happen if the view is removed mid touch).
315         if (v.getWindowToken() == null) return;
316 
317         Object tag = v.getTag();
318         if (tag instanceof ItemInfo) {
319             ItemInfo item = (ItemInfo) tag;
320             Intent intent;
321             if (item instanceof PromiseAppInfo) {
322                 PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
323                 intent = promiseAppInfo.getMarketIntent(this);
324             } else {
325                 intent = item.getIntent();
326             }
327             if (intent == null) {
328                 throw new IllegalArgumentException("Input must have a valid intent");
329             }
330             startActivitySafely(v, intent, item, CONTAINER_ALL_APPS);
331         }
332     }
333 }
334