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 androidx.annotation.Nullable; 34 35 import com.android.launcher3.LauncherSettings.Favorites; 36 import com.android.launcher3.compat.LauncherAppsCompat; 37 import com.android.launcher3.model.AppLaunchTracker; 38 import com.android.launcher3.shortcuts.DeepShortcutManager; 39 import com.android.launcher3.uioverrides.DisplayRotationListener; 40 import com.android.launcher3.uioverrides.WallpaperColorInfo; 41 import com.android.launcher3.util.PackageManagerHelper; 42 import com.android.launcher3.util.Themes; 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 returnToHomescreen()123 public void returnToHomescreen() { 124 // no-op 125 } 126 getViewBounds(View v)127 public Rect getViewBounds(View v) { 128 int[] pos = new int[2]; 129 v.getLocationOnScreen(pos); 130 return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()); 131 } 132 getActivityLaunchOptionsAsBundle(View v)133 public final Bundle getActivityLaunchOptionsAsBundle(View v) { 134 ActivityOptions activityOptions = getActivityLaunchOptions(v); 135 return activityOptions == null ? null : activityOptions.toBundle(); 136 } 137 getActivityLaunchOptions(View v)138 public abstract ActivityOptions getActivityLaunchOptions(View v); 139 startActivitySafely(View v, Intent intent, @Nullable ItemInfo item, @Nullable String sourceContainer)140 public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item, 141 @Nullable String sourceContainer) { 142 if (mIsSafeModeEnabled && !PackageManagerHelper.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 startActivity(intent, optsBundle); 166 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), 167 Process.myUserHandle(), sourceContainer); 168 } else { 169 LauncherAppsCompat.getInstance(this).startActivityForProfile( 170 intent.getComponent(), user, intent.getSourceBounds(), optsBundle); 171 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user, 172 sourceContainer); 173 } 174 getUserEventDispatcher().logAppLaunch(v, intent); 175 getStatsLogManager().logAppLaunch(v, intent); 176 return true; 177 } catch (NullPointerException|ActivityNotFoundException|SecurityException e) { 178 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 179 Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); 180 } 181 return false; 182 } 183 startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info, @Nullable String sourceContainer)184 private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info, 185 @Nullable String sourceContainer) { 186 try { 187 StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); 188 try { 189 // Temporarily disable deathPenalty on all default checks. For eg, shortcuts 190 // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure 191 // is enabled by default on NYC. 192 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() 193 .penaltyLog().build()); 194 195 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 196 String id = ((WorkspaceItemInfo) info).getDeepShortcutId(); 197 String packageName = intent.getPackage(); 198 DeepShortcutManager.getInstance(this).startShortcut( 199 packageName, id, intent.getSourceBounds(), optsBundle, info.user); 200 AppLaunchTracker.INSTANCE.get(this).onStartShortcut(packageName, id, info.user, 201 sourceContainer); 202 } else { 203 // Could be launching some bookkeeping activity 204 startActivity(intent, optsBundle); 205 } 206 } finally { 207 StrictMode.setVmPolicy(oldPolicy); 208 } 209 } catch (SecurityException e) { 210 if (!onErrorStartingShortcut(intent, info)) { 211 throw e; 212 } 213 } 214 } 215 onErrorStartingShortcut(Intent intent, ItemInfo info)216 protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { 217 return false; 218 } 219 220 @Override onStart()221 protected void onStart() { 222 super.onStart(); 223 224 if (mOnStartCallback != null) { 225 mOnStartCallback.onActivityStart(this); 226 mOnStartCallback = null; 227 } 228 } 229 230 @Override onDestroy()231 protected void onDestroy() { 232 super.onDestroy(); 233 WallpaperColorInfo.getInstance(this).removeOnChangeListener(this); 234 mRotationListener.disable(); 235 } 236 setOnStartCallback(OnStartCallback<T> callback)237 public <T extends BaseDraggingActivity> void setOnStartCallback(OnStartCallback<T> callback) { 238 mOnStartCallback = callback; 239 } 240 onDeviceProfileInitiated()241 protected void onDeviceProfileInitiated() { 242 if (mDeviceProfile.isVerticalBarLayout()) { 243 mRotationListener.enable(); 244 mDeviceProfile.updateIsSeascape(this); 245 } else { 246 mRotationListener.disable(); 247 } 248 } 249 onDeviceRotationChanged()250 private void onDeviceRotationChanged() { 251 if (mDeviceProfile.updateIsSeascape(this)) { 252 reapplyUi(); 253 } 254 } 255 reapplyUi()256 protected abstract void reapplyUi(); 257 258 /** 259 * Callback for listening for onStart 260 */ 261 public interface OnStartCallback<T extends BaseDraggingActivity> { 262 onActivityStart(T activity)263 void onActivityStart(T activity); 264 } 265 } 266