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