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