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