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.os.Bundle;
22 import android.view.View;
23 import android.view.View.OnClickListener;
24 import android.view.ViewAnimationUtils;
25 import android.view.inputmethod.InputMethodManager;
26 
27 import com.android.launcher3.AbstractFloatingView;
28 import com.android.launcher3.BaseDraggingActivity;
29 import com.android.launcher3.InvariantDeviceProfile;
30 import com.android.launcher3.LauncherAppState;
31 import com.android.launcher3.LauncherModel;
32 import com.android.launcher3.R;
33 import com.android.launcher3.allapps.AllAppsContainerView;
34 import com.android.launcher3.model.BgDataModel;
35 import com.android.launcher3.model.data.AppInfo;
36 import com.android.launcher3.model.data.ItemInfo;
37 import com.android.launcher3.model.data.ItemInfoWithIcon;
38 import com.android.launcher3.popup.PopupContainerWithArrow;
39 import com.android.launcher3.popup.PopupDataProvider;
40 import com.android.launcher3.util.ComponentKey;
41 import com.android.launcher3.util.Themes;
42 import com.android.launcher3.views.BaseDragLayer;
43 
44 import java.util.HashMap;
45 
46 /**
47  * Launcher activity for secondary displays
48  */
49 public class SecondaryDisplayLauncher extends BaseDraggingActivity
50         implements BgDataModel.Callbacks {
51 
52     private LauncherModel mModel;
53 
54     private BaseDragLayer mDragLayer;
55     private AllAppsContainerView mAppsView;
56     private View mAppsButton;
57 
58     private PopupDataProvider mPopupDataProvider;
59 
60     private boolean mAppDrawerShown = false;
61 
62     @Override
onCreate(Bundle savedInstanceState)63     protected void onCreate(Bundle savedInstanceState) {
64         super.onCreate(savedInstanceState);
65         mModel = LauncherAppState.getInstance(this).getModel();
66         if (getWindow().getDecorView().isAttachedToWindow()) {
67             initUi();
68         }
69     }
70 
71     @Override
onAttachedToWindow()72     public void onAttachedToWindow() {
73         super.onAttachedToWindow();
74         initUi();
75     }
76 
initUi()77     private void initUi() {
78         if (mDragLayer != null) {
79             return;
80         }
81         InvariantDeviceProfile currentDisplayIdp = new InvariantDeviceProfile(
82                 this, getWindow().getDecorView().getDisplay());
83 
84         // Disable transpose layout and use multi-window mode so that the icons are scaled properly
85         mDeviceProfile = currentDisplayIdp.getDeviceProfile(this)
86                 .toBuilder(this)
87                 .setMultiWindowMode(true)
88                 .setTransposeLayoutWithOrientation(false)
89                 .build();
90         mDeviceProfile.autoResizeAllAppsCells();
91 
92         setContentView(R.layout.secondary_launcher);
93         mDragLayer = findViewById(R.id.drag_layer);
94         mAppsView = findViewById(R.id.apps_view);
95         mAppsButton = findViewById(R.id.all_apps_button);
96 
97         mPopupDataProvider = new PopupDataProvider(
98                 mAppsView.getAppsStore()::updateNotificationDots);
99 
100         mModel.addCallbacksAndLoad(this);
101     }
102 
103     @Override
onNewIntent(Intent intent)104     public void onNewIntent(Intent intent) {
105         super.onNewIntent(intent);
106 
107         if (Intent.ACTION_MAIN.equals(intent.getAction())) {
108             // Hide keyboard.
109             final View v = getWindow().peekDecorView();
110             if (v != null && v.getWindowToken() != null) {
111                 getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
112                         v.getWindowToken(), 0);
113             }
114         }
115 
116         // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
117         showAppDrawer(false);
118     }
119 
120     @Override
onBackPressed()121     public void onBackPressed() {
122         if (finishAutoCancelActionMode()) {
123             return;
124         }
125 
126         // Note: There should be at most one log per method call. This is enforced implicitly
127         // by using if-else statements.
128         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
129         if (topView != null && topView.onBackPressed()) {
130             // Handled by the floating view.
131         } else {
132             showAppDrawer(false);
133         }
134     }
135 
136     @Override
onDestroy()137     protected void onDestroy() {
138         super.onDestroy();
139         mModel.removeCallbacks(this);
140     }
141 
isAppDrawerShown()142     public boolean isAppDrawerShown() {
143         return mAppDrawerShown;
144     }
145 
getAppsView()146     public AllAppsContainerView getAppsView() {
147         return mAppsView;
148     }
149 
150     @Override
getOverviewPanel()151     public <T extends View> T getOverviewPanel() {
152         return null;
153     }
154 
155     @Override
getRootView()156     public View getRootView() {
157         return mDragLayer;
158     }
159 
160     @Override
reapplyUi()161     protected void reapplyUi() { }
162 
163     @Override
getDragLayer()164     public BaseDragLayer getDragLayer() {
165         return mDragLayer;
166     }
167 
168     @Override
bindIncrementalDownloadProgressUpdated(AppInfo app)169     public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
170         mAppsView.getAppsStore().updateProgressBar(app);
171     }
172 
173     /**
174      * Called when apps-button is clicked
175      */
onAppsButtonClicked(View v)176     public void onAppsButtonClicked(View v) {
177         showAppDrawer(true);
178     }
179 
180     /**
181      * Show/hide app drawer card with animation.
182      */
showAppDrawer(boolean show)183     public void showAppDrawer(boolean show) {
184         if (show == mAppDrawerShown) {
185             return;
186         }
187 
188         float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight());
189         float closeR = Themes.getDialogCornerRadius(this);
190         float startR = mAppsButton.getWidth() / 2f;
191 
192         float[] buttonPos = new float[] { startR, startR};
193         mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
194         mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
195         final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
196                 (int) buttonPos[0], (int) buttonPos[1],
197                 show ? closeR : openR, show ? openR : closeR);
198 
199         if (show) {
200             mAppDrawerShown = true;
201             mAppsView.setVisibility(View.VISIBLE);
202             mAppsButton.setVisibility(View.INVISIBLE);
203         } else {
204             mAppDrawerShown = false;
205             animator.addListener(new AnimatorListenerAdapter() {
206                 @Override
207                 public void onAnimationEnd(Animator animation) {
208                     mAppsView.setVisibility(View.INVISIBLE);
209                     mAppsButton.setVisibility(View.VISIBLE);
210                     mAppsView.getSearchUiManager().resetSearch();
211                 }
212             });
213         }
214         animator.start();
215     }
216 
217     @Override
bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap)218     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
219         mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
220     }
221 
222     @Override
bindAllApplications(AppInfo[] apps, int flags)223     public void bindAllApplications(AppInfo[] apps, int flags) {
224         mAppsView.getAppsStore().setApps(apps, flags);
225         PopupContainerWithArrow.dismissInvalidPopup(this);
226     }
227 
getPopupDataProvider()228     public PopupDataProvider getPopupDataProvider() {
229         return mPopupDataProvider;
230     }
231 
232     @Override
getItemOnClickListener()233     public OnClickListener getItemOnClickListener() {
234         return this::onIconClicked;
235     }
236 
onIconClicked(View v)237     private void onIconClicked(View v) {
238         // Make sure that rogue clicks don't get through while allapps is launching, or after the
239         // view has detached (it's possible for this to happen if the view is removed mid touch).
240         if (v.getWindowToken() == null) return;
241 
242         Object tag = v.getTag();
243         if (tag instanceof ItemInfo) {
244             ItemInfo item = (ItemInfo) tag;
245             Intent intent;
246             if (item instanceof ItemInfoWithIcon
247                     && (((ItemInfoWithIcon) item).runtimeStatusFlags
248                         & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
249                 ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
250                 intent = appInfo.getMarketIntent(this);
251             } else {
252                 intent = item.getIntent();
253             }
254             if (intent == null) {
255                 throw new IllegalArgumentException("Input must have a valid intent");
256             }
257             startActivitySafely(v, intent, item);
258         }
259     }
260 }
261