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.View.OnClickListener; 45 import android.view.WindowInsets.Type; 46 import android.view.WindowMetrics; 47 import android.widget.Toast; 48 49 import androidx.annotation.NonNull; 50 import androidx.annotation.Nullable; 51 52 import com.android.launcher3.LauncherSettings.Favorites; 53 import com.android.launcher3.allapps.AllAppsContainerView; 54 import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider; 55 import com.android.launcher3.allapps.search.SearchAdapterProvider; 56 import com.android.launcher3.logging.InstanceId; 57 import com.android.launcher3.logging.InstanceIdSequence; 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 getViewBounds(View v)168 public Rect getViewBounds(View v) { 169 int[] pos = new int[2]; 170 v.getLocationOnScreen(pos); 171 return new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1] + v.getHeight()); 172 } 173 174 @NonNull getActivityLaunchOptions(View v, @Nullable ItemInfo item)175 public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { 176 int left = 0, top = 0; 177 int width = v.getMeasuredWidth(), height = v.getMeasuredHeight(); 178 if (v instanceof BubbleTextView) { 179 // Launch from center of icon, not entire view 180 Drawable icon = ((BubbleTextView) v).getIcon(); 181 if (icon != null) { 182 Rect bounds = icon.getBounds(); 183 left = (width - bounds.width()) / 2; 184 top = v.getPaddingTop(); 185 width = bounds.width(); 186 height = bounds.height(); 187 } 188 } 189 ActivityOptions options = 190 ActivityOptions.makeClipRevealAnimation(v, left, top, width, height); 191 RunnableList callback = new RunnableList(); 192 addOnResumeCallback(callback::executeAllAndDestroy); 193 return new ActivityOptionsWrapper(options, callback); 194 } 195 startActivitySafely(View v, Intent intent, @Nullable ItemInfo item)196 public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) { 197 if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { 198 Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); 199 return false; 200 } 201 202 Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v, item).toBundle() : null; 203 UserHandle user = item == null ? null : item.user; 204 205 // Prepare intent 206 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 207 if (v != null) { 208 intent.setSourceBounds(getViewBounds(v)); 209 } 210 try { 211 boolean isShortcut = (item instanceof WorkspaceItemInfo) 212 && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT 213 || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) 214 && !((WorkspaceItemInfo) item).isPromise(); 215 if (isShortcut) { 216 // Shortcuts need some special checks due to legacy reasons. 217 startShortcutIntentSafely(intent, optsBundle, item); 218 } else if (user == null || user.equals(Process.myUserHandle())) { 219 // Could be launching some bookkeeping activity 220 startActivity(intent, optsBundle); 221 } else { 222 getSystemService(LauncherApps.class).startMainActivity( 223 intent.getComponent(), user, intent.getSourceBounds(), optsBundle); 224 } 225 if (item != null) { 226 InstanceId instanceId = new InstanceIdSequence().newInstanceId(); 227 logAppLaunch(item, instanceId); 228 } 229 return true; 230 } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { 231 Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); 232 Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); 233 } 234 return false; 235 } 236 logAppLaunch(ItemInfo info, InstanceId instanceId)237 protected void logAppLaunch(ItemInfo info, InstanceId instanceId) { 238 getStatsLogManager().logger().withItemInfo(info).withInstanceId(instanceId) 239 .log(LAUNCHER_APP_LAUNCH_TAP); 240 } 241 startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info)242 private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) { 243 try { 244 StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); 245 try { 246 // Temporarily disable deathPenalty on all default checks. For eg, shortcuts 247 // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure 248 // is enabled by default on NYC. 249 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() 250 .penaltyLog().build()); 251 252 if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { 253 String id = ((WorkspaceItemInfo) info).getDeepShortcutId(); 254 String packageName = intent.getPackage(); 255 startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user); 256 } else { 257 // Could be launching some bookkeeping activity 258 startActivity(intent, optsBundle); 259 } 260 } finally { 261 StrictMode.setVmPolicy(oldPolicy); 262 } 263 } catch (SecurityException e) { 264 if (!onErrorStartingShortcut(intent, info)) { 265 throw e; 266 } 267 } 268 } 269 onErrorStartingShortcut(Intent intent, ItemInfo info)270 protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { 271 return false; 272 } 273 274 @Override onStart()275 protected void onStart() { 276 super.onStart(); 277 278 if (mOnStartCallback != null) { 279 mOnStartCallback.run(); 280 mOnStartCallback = null; 281 } 282 } 283 284 @Override onDestroy()285 protected void onDestroy() { 286 super.onDestroy(); 287 if (Utilities.ATLEAST_P) { 288 getSystemService(WallpaperManager.class).removeOnColorsChangedListener(this); 289 } 290 DisplayController.INSTANCE.get(this).removeChangeListener(this); 291 } 292 runOnceOnStart(Runnable action)293 public void runOnceOnStart(Runnable action) { 294 mOnStartCallback = action; 295 } 296 clearRunOnceOnStartCallback()297 public void clearRunOnceOnStartCallback() { 298 mOnStartCallback = null; 299 } 300 onDeviceProfileInitiated()301 protected void onDeviceProfileInitiated() { 302 if (mDeviceProfile.isVerticalBarLayout()) { 303 mDeviceProfile.updateIsSeascape(this); 304 } 305 } 306 307 @Override onDisplayInfoChanged(Context context, Info info, int flags)308 public void onDisplayInfoChanged(Context context, Info info, int flags) { 309 if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.updateIsSeascape(this)) { 310 reapplyUi(); 311 } 312 } 313 getItemOnClickListener()314 public OnClickListener getItemOnClickListener() { 315 return ItemClickHandler.INSTANCE; 316 } 317 reapplyUi()318 protected abstract void reapplyUi(); 319 getMultiWindowDisplaySize()320 protected WindowBounds getMultiWindowDisplaySize() { 321 if (Utilities.ATLEAST_R) { 322 WindowMetrics wm = getWindowManager().getCurrentWindowMetrics(); 323 324 Insets insets = wm.getWindowInsets().getInsets(Type.systemBars()); 325 return new WindowBounds(wm.getBounds(), 326 new Rect(insets.left, insets.top, insets.right, insets.bottom)); 327 } 328 // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return 329 // the app window size 330 Display display = getWindowManager().getDefaultDisplay(); 331 Point mwSize = new Point(); 332 display.getSize(mwSize); 333 return new WindowBounds(new Rect(0, 0, mwSize.x, mwSize.y), new Rect()); 334 } 335 336 /** 337 * Creates and returns {@link SearchAdapterProvider} for build variant specific search result 338 * views 339 */ createSearchAdapterProvider(AllAppsContainerView allapps)340 public SearchAdapterProvider createSearchAdapterProvider(AllAppsContainerView allapps) { 341 return new DefaultSearchAdapterProvider(this, allapps); 342 } 343 } 344