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