1 /* 2 * Copyright (C) 2018 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 17 package com.android.launcher3; 18 19 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; 20 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION; 21 22 import android.app.ActivityOptions; 23 import android.content.ActivityNotFoundException; 24 import android.content.Intent; 25 import android.content.pm.LauncherApps; 26 import android.content.res.Configuration; 27 import android.graphics.Insets; 28 import android.graphics.Point; 29 import android.graphics.Rect; 30 import android.os.Bundle; 31 import android.os.Process; 32 import android.os.StrictMode; 33 import android.os.UserHandle; 34 import android.util.Log; 35 import android.view.ActionMode; 36 import android.view.Display; 37 import android.view.View; 38 import android.view.View.OnClickListener; 39 import android.view.WindowInsets.Type; 40 import android.view.WindowMetrics; 41 import android.widget.Toast; 42 43 import androidx.annotation.Nullable; 44 45 import com.android.launcher3.LauncherSettings.Favorites; 46 import com.android.launcher3.logging.InstanceId; 47 import com.android.launcher3.logging.InstanceIdSequence; 48 import com.android.launcher3.model.AppLaunchTracker; 49 import com.android.launcher3.model.data.ItemInfo; 50 import com.android.launcher3.model.data.WorkspaceItemInfo; 51 import com.android.launcher3.touch.ItemClickHandler; 52 import com.android.launcher3.uioverrides.WallpaperColorInfo; 53 import com.android.launcher3.util.DefaultDisplay; 54 import com.android.launcher3.util.DefaultDisplay.DisplayInfoChangeListener; 55 import com.android.launcher3.util.DefaultDisplay.Info; 56 import com.android.launcher3.util.PackageManagerHelper; 57 import com.android.launcher3.util.Themes; 58 import com.android.launcher3.util.TraceHelper; 59 import com.android.launcher3.util.WindowBounds; 60 61 /** 62 * Extension of BaseActivity allowing support for drag-n-drop 63 */ 64 public abstract class BaseDraggingActivity extends BaseActivity 65 implements WallpaperColorInfo.OnChangeListener, DisplayInfoChangeListener { 66 67 private static final String TAG = "BaseDraggingActivity"; 68 69 // When starting an action mode, setting this tag will cause the action mode to be cancelled 70 // automatically when user interacts with the launcher. 71 public static final Object AUTO_CANCEL_ACTION_MODE = new Object(); 72 73 private ActionMode mCurrentActionMode; 74 protected boolean mIsSafeModeEnabled; 75 76 private Runnable mOnStartCallback; 77 78 private int mThemeRes = R.style.AppTheme; 79 80 @Override onCreate(Bundle savedInstanceState)81 protected void onCreate(Bundle savedInstanceState) { 82 super.onCreate(savedInstanceState); 83 84 85 mIsSafeModeEnabled = TraceHelper.whitelistIpcs("isSafeMode", 86 () -> getPackageManager().isSafeMode()); 87 DefaultDisplay.INSTANCE.get(this).addChangeListener(this); 88 89 // Update theme 90 WallpaperColorInfo.INSTANCE.get(this).addOnChangeListener(this); 91 int themeRes = Themes.getActivityThemeRes(this); 92 if (themeRes != mThemeRes) { 93 mThemeRes = themeRes; 94 setTheme(themeRes); 95 } 96 } 97 98 @Override onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo)99 public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) { 100 updateTheme(); 101 } 102 103 @Override onConfigurationChanged(Configuration newConfig)104 public void onConfigurationChanged(Configuration newConfig) { 105 super.onConfigurationChanged(newConfig); 106 updateTheme(); 107 } 108 updateTheme()109 private void updateTheme() { 110 if (mThemeRes != Themes.getActivityThemeRes(this)) { 111 recreate(); 112 } 113 } 114 115 @Override onActionModeStarted(ActionMode mode)116 public void onActionModeStarted(ActionMode mode) { 117 super.onActionModeStarted(mode); 118 mCurrentActionMode = mode; 119 } 120 121 @Override onActionModeFinished(ActionMode mode)122 public void onActionModeFinished(ActionMode mode) { 123 super.onActionModeFinished(mode); 124 mCurrentActionMode = null; 125 } 126 127 @Override finishAutoCancelActionMode()128 public boolean finishAutoCancelActionMode() { 129 if (mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag()) { 130 mCurrentActionMode.finish(); 131 return true; 132 } 133 return false; 134 } 135 getOverviewPanel()136 public abstract <T extends View> T getOverviewPanel(); 137 getRootView()138 public abstract View getRootView(); 139 returnToHomescreen()140 public void returnToHomescreen() { 141 // no-op 142 } 143 getViewBounds(View v)144 public Rect getViewBounds(View v) { 145 int[] pos = new int[2]; 146 v.getLocationOnScreen(pos); 147 return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()); 148 } 149 getActivityLaunchOptionsAsBundle(View v)150 public final Bundle getActivityLaunchOptionsAsBundle(View v) { 151 ActivityOptions activityOptions = getActivityLaunchOptions(v); 152 return activityOptions == null ? null : activityOptions.toBundle(); 153 } 154 getActivityLaunchOptions(View v)155 public abstract ActivityOptions getActivityLaunchOptions(View v); 156 startActivitySafely(View v, Intent intent, @Nullable ItemInfo item, @Nullable String sourceContainer)157 public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item, 158 @Nullable String sourceContainer) { 159 if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { 160 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); 161 return false; 162 } 163 164 Bundle optsBundle = (v != null) ? getActivityLaunchOptionsAsBundle(v) : null; 165 UserHandle user = item == null ? null : item.user; 166 167 // Prepare intent 168 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 169 if (v != null) { 170 intent.setSourceBounds(getViewBounds(v)); 171 } 172 try { 173 boolean isShortcut = (item instanceof WorkspaceItemInfo) 174 && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT 175 || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) 176 && !((WorkspaceItemInfo) item).isPromise(); 177 if (isShortcut) { 178 // Shortcuts need some special checks due to legacy reasons. 179 startShortcutIntentSafely(intent, optsBundle, item, sourceContainer); 180 } else if (user == null || user.equals(Process.myUserHandle())) { 181 // Could be launching some bookkeeping activity 182 startActivity(intent, optsBundle); 183 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), 184 Process.myUserHandle(), sourceContainer); 185 } else { 186 getSystemService(LauncherApps.class).startMainActivity( 187 intent.getComponent(), user, intent.getSourceBounds(), optsBundle); 188 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user, 189 sourceContainer); 190 } 191 getUserEventDispatcher().logAppLaunch(v, intent, user); 192 if (item != null) { 193 InstanceId instanceId = new InstanceIdSequence().newInstanceId(); 194 logAppLaunch(item, instanceId); 195 } 196 return true; 197 } catch (NullPointerException|ActivityNotFoundException|SecurityException e) { 198 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 199 Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); 200 } 201 return false; 202 } 203 logAppLaunch(ItemInfo info, InstanceId instanceId)204 protected void logAppLaunch(ItemInfo info, InstanceId instanceId) { 205 getStatsLogManager().logger().withItemInfo(info).withInstanceId(instanceId) 206 .log(LAUNCHER_APP_LAUNCH_TAP); 207 } 208 startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info, @Nullable String sourceContainer)209 private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info, 210 @Nullable String sourceContainer) { 211 try { 212 StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); 213 try { 214 // Temporarily disable deathPenalty on all default checks. For eg, shortcuts 215 // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure 216 // is enabled by default on NYC. 217 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() 218 .penaltyLog().build()); 219 220 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 221 String id = ((WorkspaceItemInfo) info).getDeepShortcutId(); 222 String packageName = intent.getPackage(); 223 startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user); 224 AppLaunchTracker.INSTANCE.get(this).onStartShortcut(packageName, id, info.user, 225 sourceContainer); 226 } else { 227 // Could be launching some bookkeeping activity 228 startActivity(intent, optsBundle); 229 } 230 } finally { 231 StrictMode.setVmPolicy(oldPolicy); 232 } 233 } catch (SecurityException e) { 234 if (!onErrorStartingShortcut(intent, info)) { 235 throw e; 236 } 237 } 238 } 239 onErrorStartingShortcut(Intent intent, ItemInfo info)240 protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { 241 return false; 242 } 243 244 @Override onStart()245 protected void onStart() { 246 super.onStart(); 247 248 if (mOnStartCallback != null) { 249 mOnStartCallback.run(); 250 mOnStartCallback = null; 251 } 252 } 253 254 @Override onDestroy()255 protected void onDestroy() { 256 super.onDestroy(); 257 WallpaperColorInfo.INSTANCE.get(this).removeOnChangeListener(this); 258 DefaultDisplay.INSTANCE.get(this).removeChangeListener(this); 259 } 260 runOnceOnStart(Runnable action)261 public void runOnceOnStart(Runnable action) { 262 mOnStartCallback = action; 263 } 264 clearRunOnceOnStartCallback()265 public void clearRunOnceOnStartCallback() { 266 mOnStartCallback = null; 267 } 268 onDeviceProfileInitiated()269 protected void onDeviceProfileInitiated() { 270 if (mDeviceProfile.isVerticalBarLayout()) { 271 mDeviceProfile.updateIsSeascape(this); 272 } 273 } 274 275 @Override onDisplayInfoChanged(Info info, int flags)276 public void onDisplayInfoChanged(Info info, int flags) { 277 if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.updateIsSeascape(this)) { 278 reapplyUi(); 279 } 280 } 281 getItemOnClickListener()282 public OnClickListener getItemOnClickListener() { 283 return ItemClickHandler.INSTANCE; 284 } 285 reapplyUi()286 protected abstract void reapplyUi(); 287 getMultiWindowDisplaySize()288 protected WindowBounds getMultiWindowDisplaySize() { 289 if (Utilities.ATLEAST_R) { 290 WindowMetrics wm = getWindowManager().getCurrentWindowMetrics(); 291 292 Insets insets = wm.getWindowInsets().getInsets(Type.systemBars()); 293 return new WindowBounds(wm.getBounds(), 294 new Rect(insets.left, insets.top, insets.right, insets.bottom)); 295 } 296 // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return 297 // the app window size 298 Display display = getWindowManager().getDefaultDisplay(); 299 Point mwSize = new Point(); 300 display.getSize(mwSize); 301 return new WindowBounds(new Rect(0, 0, mwSize.x, mwSize.y), new Rect()); 302 } 303 } 304