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