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 android.app.ActivityOptions; 20 import android.content.ActivityNotFoundException; 21 import android.content.Context; 22 import android.content.ContextWrapper; 23 import android.content.Intent; 24 import android.graphics.Rect; 25 import android.os.Bundle; 26 import android.os.Process; 27 import android.os.StrictMode; 28 import android.os.UserHandle; 29 import android.util.Log; 30 import android.view.ActionMode; 31 import android.view.Surface; 32 import android.view.View; 33 import android.widget.Toast; 34 35 import com.android.launcher3.LauncherSettings.Favorites; 36 import com.android.launcher3.badge.BadgeInfo; 37 import com.android.launcher3.compat.LauncherAppsCompat; 38 import com.android.launcher3.uioverrides.DisplayRotationListener; 39 import com.android.launcher3.uioverrides.WallpaperColorInfo; 40 import com.android.launcher3.shortcuts.DeepShortcutManager; 41 import com.android.launcher3.views.BaseDragLayer; 42 43 /** 44 * Extension of BaseActivity allowing support for drag-n-drop 45 */ 46 public abstract class BaseDraggingActivity extends BaseActivity 47 implements WallpaperColorInfo.OnChangeListener { 48 49 private static final String TAG = "BaseDraggingActivity"; 50 51 // The Intent extra that defines whether to ignore the launch animation 52 public static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION = 53 "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION"; 54 55 // When starting an action mode, setting this tag will cause the action mode to be cancelled 56 // automatically when user interacts with the launcher. 57 public static final Object AUTO_CANCEL_ACTION_MODE = new Object(); 58 59 private ActionMode mCurrentActionMode; 60 protected boolean mIsSafeModeEnabled; 61 62 private OnStartCallback mOnStartCallback; 63 64 private int mThemeRes = R.style.LauncherTheme; 65 66 private DisplayRotationListener mRotationListener; 67 68 @Override 69 protected void onCreate(Bundle savedInstanceState) { 70 super.onCreate(savedInstanceState); 71 mIsSafeModeEnabled = getPackageManager().isSafeMode(); 72 mRotationListener = new DisplayRotationListener(this, this::onDeviceRotationChanged); 73 74 // Update theme 75 WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this); 76 wallpaperColorInfo.addOnChangeListener(this); 77 int themeRes = getThemeRes(wallpaperColorInfo); 78 if (themeRes != mThemeRes) { 79 mThemeRes = themeRes; 80 setTheme(themeRes); 81 } 82 } 83 84 @Override 85 public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) { 86 if (mThemeRes != getThemeRes(wallpaperColorInfo)) { 87 recreate(); 88 } 89 } 90 91 protected int getThemeRes(WallpaperColorInfo wallpaperColorInfo) { 92 if (wallpaperColorInfo.isDark()) { 93 return wallpaperColorInfo.supportsDarkText() ? 94 R.style.LauncherThemeDark_DarKText : R.style.LauncherThemeDark; 95 } else { 96 return wallpaperColorInfo.supportsDarkText() ? 97 R.style.LauncherTheme_DarkText : R.style.LauncherTheme; 98 } 99 } 100 101 @Override 102 public void onActionModeStarted(ActionMode mode) { 103 super.onActionModeStarted(mode); 104 mCurrentActionMode = mode; 105 } 106 107 @Override 108 public void onActionModeFinished(ActionMode mode) { 109 super.onActionModeFinished(mode); 110 mCurrentActionMode = null; 111 } 112 113 public boolean finishAutoCancelActionMode() { 114 if (mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag()) { 115 mCurrentActionMode.finish(); 116 return true; 117 } 118 return false; 119 } 120 121 public abstract BaseDragLayer getDragLayer(); 122 123 public abstract <T extends View> T getOverviewPanel(); 124 125 public abstract View getRootView(); 126 127 public abstract BadgeInfo getBadgeInfoForItem(ItemInfo info); 128 129 public abstract void invalidateParent(ItemInfo info); 130 131 public static BaseDraggingActivity fromContext(Context context) { 132 if (context instanceof BaseDraggingActivity) { 133 return (BaseDraggingActivity) context; 134 } 135 return ((BaseDraggingActivity) ((ContextWrapper) context).getBaseContext()); 136 } 137 138 public Rect getViewBounds(View v) { 139 int[] pos = new int[2]; 140 v.getLocationOnScreen(pos); 141 return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()); 142 } 143 144 public final Bundle getActivityLaunchOptionsAsBundle(View v) { 145 ActivityOptions activityOptions = getActivityLaunchOptions(v); 146 return activityOptions == null ? null : activityOptions.toBundle(); 147 } 148 149 public abstract ActivityOptions getActivityLaunchOptions(View v); 150 151 public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { 152 if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { 153 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); 154 return false; 155 } 156 157 // Only launch using the new animation if the shortcut has not opted out (this is a 158 // private contract between launcher and may be ignored in the future). 159 boolean useLaunchAnimation = (v != null) && 160 !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION); 161 Bundle optsBundle = useLaunchAnimation 162 ? getActivityLaunchOptionsAsBundle(v) 163 : null; 164 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 = Utilities.ATLEAST_MARSHMALLOW 174 && (item instanceof ShortcutInfo) 175 && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT 176 || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) 177 && !((ShortcutInfo) item).isPromise(); 178 if (isShortcut) { 179 // Shortcuts need some special checks due to legacy reasons. 180 startShortcutIntentSafely(intent, optsBundle, item); 181 } else if (user == null || user.equals(Process.myUserHandle())) { 182 // Could be launching some bookkeeping activity 183 startActivity(intent, optsBundle); 184 } else { 185 LauncherAppsCompat.getInstance(this).startActivityForProfile( 186 intent.getComponent(), user, intent.getSourceBounds(), optsBundle); 187 } 188 getUserEventDispatcher().logAppLaunch(v, intent); 189 return true; 190 } catch (ActivityNotFoundException|SecurityException e) { 191 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 192 Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); 193 } 194 return false; 195 } 196 197 private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) { 198 try { 199 StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); 200 try { 201 // Temporarily disable deathPenalty on all default checks. For eg, shortcuts 202 // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure 203 // is enabled by default on NYC. 204 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() 205 .penaltyLog().build()); 206 207 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 208 String id = ((ShortcutInfo) info).getDeepShortcutId(); 209 String packageName = intent.getPackage(); 210 DeepShortcutManager.getInstance(this).startShortcut( 211 packageName, id, intent.getSourceBounds(), optsBundle, info.user); 212 } else { 213 // Could be launching some bookkeeping activity 214 startActivity(intent, optsBundle); 215 } 216 } finally { 217 StrictMode.setVmPolicy(oldPolicy); 218 } 219 } catch (SecurityException e) { 220 if (!onErrorStartingShortcut(intent, info)) { 221 throw e; 222 } 223 } 224 } 225 226 protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { 227 return false; 228 } 229 230 @Override 231 protected void onStart() { 232 super.onStart(); 233 234 if (mOnStartCallback != null) { 235 mOnStartCallback.onActivityStart(this); 236 mOnStartCallback = null; 237 } 238 } 239 240 @Override 241 protected void onDestroy() { 242 super.onDestroy(); 243 WallpaperColorInfo.getInstance(this).removeOnChangeListener(this); 244 mRotationListener.disable(); 245 } 246 247 public <T extends BaseDraggingActivity> void setOnStartCallback(OnStartCallback<T> callback) { 248 mOnStartCallback = callback; 249 } 250 251 protected void onDeviceProfileInitiated() { 252 if (mDeviceProfile.isVerticalBarLayout()) { 253 mRotationListener.enable(); 254 mDeviceProfile.updateIsSeascape(getWindowManager()); 255 } else { 256 mRotationListener.disable(); 257 } 258 } 259 260 private void onDeviceRotationChanged() { 261 if (mDeviceProfile.updateIsSeascape(getWindowManager())) { 262 reapplyUi(); 263 } 264 } 265 266 protected abstract void reapplyUi(); 267 268 /** 269 * Callback for listening for onStart 270 */ 271 public interface OnStartCallback<T extends BaseDraggingActivity> { 272 273 void onActivityStart(T activity); 274 } 275 } 276