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