Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a6a98873 authored by Nextbit's avatar Nextbit Committed by Steve Kondik
Browse files

Recents long press: add pluggable implementation

- Adds a Secure.Setting for the activity component to handle
  Intent.ACTION_RECENTS_LONG_PRESS.
- Adds a new intent ACTION_RECENTS_LONG_PRESS for the
  long press of the recents button. Other system applications
  can register for the intent to provide custom handling.
  Handlers of this intent are queried for in the settings
  application, and if no handlers are found, the default
  implementation falls back on ActionUtils.switchToLastApp().

  The long press listener on the recents button is replaced
  with a touch handler to allow for more custom handling.

For cm-12.0, the following modifications were made:

- With the full deprecation of ActivityManager.getRecentTasks(),
  applications can not query for the last foreground application.
  To aid these applications, the framework will include an
  EXTRA_CURRENT_PACKAGE_NAME with ACTION_RECENTS_LONG_PRESS.
- Instead of re-evaluating packages on every Long Press, PhoneStatusBar
  now tracks Settings changes and package activities and
  incrementally caches the desired Recents long press behavior.

This is a roll-up CR from cm-11.0, and also contains the following Change-Ids:
- I689e8b5c26b39e100cb166d2f93e7ee49e83f93a

Change-Id: I8a27ae0fe58d92a1626f0bd661c5f57c589fac17
parent 61162522
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -1311,6 +1311,15 @@ public class Intent implements Parcelable, Cloneable {
    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS";

    /**
     * Activity Action: Start action associated with long press on the recents key.
     * <p>Input: {@link #EXTRA_LONG_PRESS_RELEASE} is set to true if the long press
     * is released
     * <p>Output: Nothing
     * @hide
     */
    public static final String ACTION_RECENTS_LONG_PRESS = "android.intent.action.RECENTS_LONG_PRESS";

    /**
     * Activity Action: The user pressed the "Report" button in the crash/ANR dialog.
     * This intent is delivered to the package which installed the application, usually
@@ -3570,6 +3579,15 @@ public class Intent implements Parcelable, Cloneable {
    public static final String EXTRA_SHUTDOWN_USERSPACE_ONLY
            = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";

    /**
     * This field is part of the intent {@link #ACTION_RECENTS_LONG_PRESS}.
     * The type of the extra is a boolean that indicates if the long press
     * is released.
     * @hide
     */
    public static final String EXTRA_RECENTS_LONG_PRESS_RELEASE =
            "android.intent.extra.RECENTS_LONG_PRESS_RELEASE";

    /**
     * Optional boolean extra for {@link #ACTION_TIME_CHANGED} that indicates the
     * user has set their time format preferences to the 24 hour format.
@@ -3602,6 +3620,14 @@ public class Intent implements Parcelable, Cloneable {
     */
    public static final String EXTRA_THEME_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";

    /**
     * Extra for {@link #ACTION_RECENTS_LONG_PRESS} that provides the package name of the
     * app in foreground when recents was long pressed. Can be reused for other purposes.
     * @hide
     */
    public static final String EXTRA_CURRENT_PACKAGE_NAME =
            "com.cyanogenmod.intent.extra.CURRENT_PACKAGE_NAME";

    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
    // Intent flags (see mFlags variable).
+9 −0
Original line number Diff line number Diff line
@@ -5881,6 +5881,15 @@ public final class Settings {
                "navigation_ring_targets_2",
        };

        /**
         * The global recents long press activity chosen by the user.
         * This setting is stored as a flattened component name as
         * per {@link ComponentName#flattenToString()}.
         *
         * @hide
         */
        public static final String RECENTS_LONG_PRESS_ACTIVITY = "recents_long_press_activity";

        /**
         * Whether to enable "advanced mode" for the current user.
         * Boolean setting. 0 = no, 1 = yes.
+0 −20
Original line number Diff line number Diff line
@@ -1121,26 +1121,6 @@ public abstract class BaseStatusBar extends SystemUI implements

    protected abstract View getStatusBarView();

    protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
        // additional optimization when we have software system buttons - start loading the recent
        // tasks on touch down
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction() & MotionEvent.ACTION_MASK;
            if (action == MotionEvent.ACTION_DOWN) {
                preloadRecents();
            } else if (action == MotionEvent.ACTION_CANCEL) {
                cancelPreloadingRecents();
            } else if (action == MotionEvent.ACTION_UP) {
                if (!v.isPressed()) {
                    cancelPreloadingRecents();
                }

            }
            return false;
        }
    };

    /** Proxy for RecentsComponent */

    protected void showRecents(boolean triggeredFromAltTab) {
+8 −0
Original line number Diff line number Diff line
@@ -251,6 +251,14 @@ public class NavigationBarView extends LinearLayout {
        mDelegateHelper.setBar(phoneStatusBar);
    }

    public void disableSearchBar() {
        mDelegateHelper.setDisabled(true);
    }

    public void enableSearchBar() {
        mDelegateHelper.setDisabled(false);
    }

    public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
        mOnVerticalChangedListener = onVerticalChangedListener;
        notifyVerticalChangedListener(mVertical);
+207 −1
Original line number Diff line number Diff line
@@ -43,7 +43,9 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.WallpaperManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -425,6 +427,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        }
    };

    // Custom Recents Long Press
    // - Tracks Event state for custom (user-configurable) Long Presses.
    private boolean mCustomRecentsLongPressed = false;
    // - The ArrayList is updated when packages are added and removed.
    private List<ComponentName> mCustomRecentsLongPressHandlerCandidates = new ArrayList<>();
    // - The custom Recents Long Press, if selected.  When null, use default (switch last app).
    private ComponentName mCustomRecentsLongPressHandler = null;

    class SettingsObserver extends UserContentObserver {
        SettingsObserver(Handler handler) {
            super(handler);
@@ -444,6 +454,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
                    Settings.System.STATUS_BAR_CLOCK), false, this, UserHandle.USER_ALL);
            resolver.registerContentObserver(Settings.System.getUriFor(
                    Settings.System.NAVBAR_LEFT_IN_LANDSCAPE), false, this, UserHandle.USER_ALL);
            resolver.registerContentObserver(Settings.System.getUriFor(
                    Settings.System.STATUS_BAR_CLOCK), false, this);
            resolver.registerContentObserver(Settings.Secure.getUriFor(
                    Settings.Secure.RECENTS_LONG_PRESS_ACTIVITY), false, this);
            update();
        }

@@ -498,6 +512,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
                        Settings.System.NAVBAR_LEFT_IN_LANDSCAPE, 0, UserHandle.USER_CURRENT) == 1;
                mNavigationBarView.setLeftInLandscape(navLeftInLandscape);
            }

            // This method reads Settings.Secure.RECENTS_LONG_PRESS_ACTIVITY
            updateCustomRecentsLongPressHandler(false);
        }
    }

@@ -860,6 +877,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        notifyUserAboutHiddenNotifications();

        mScreenPinningRequest = new ScreenPinningRequest(mContext);

        updateCustomRecentsLongPressHandler(true);
    }

    boolean isMSim() {
@@ -1252,6 +1271,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        filter.addAction(ACTION_DEMO);
        context.registerReceiver(mBroadcastReceiver, filter);

        // receive broadcasts for packages
        IntentFilter packageFilter = new IntentFilter();
        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        packageFilter.addDataScheme("package");
        context.registerReceiver(mPackageBroadcastReceiver, packageFilter);

        // listen for USER_SETUP_COMPLETE setting (per-user)
        resetUserSetupObserver();

@@ -1498,6 +1526,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        mNavigationBarView.reorient();
        mNavigationBarView.setListeners(mRecentsClickListener, mRecentsPreloadOnTouchListener,
                mLongPressBackRecentsListener, mHomeActionListener);

        updateSearchPanel();
    }

@@ -3616,6 +3645,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
        }
    };

    private BroadcastReceiver mPackageBroadcastReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) Log.v(TAG, "onReceive: " + intent);
            String action = intent.getAction();
            if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
                    Intent.ACTION_PACKAGE_CHANGED.equals(action) ||
                    Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action) ||
                    Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
                updateCustomRecentsLongPressHandler(true);
            }
        }
    };

    private void resetUserExpandedStates() {
        ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
        final int notificationCount = activeNotifications.size();
@@ -4682,13 +4724,177 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
            }

            if (hijackRecentsLongPress) {
                // If there is a user-selected, registered handler for the
                // recents long press, start the Intent.  Otherwise,
                // perform the default action, which is last app switching.

                // Copy it so the value doesn't change between now and when the activity is started.
                ComponentName customRecentsLongPressHandler = mCustomRecentsLongPressHandler;
                if (customRecentsLongPressHandler != null) {
                    startCustomRecentsLongPressActivity(customRecentsLongPressHandler);
                } else {
                    ActionUtils.switchToLastApp(mContext, mCurrentUserId);
                }
            }
        } catch (RemoteException e) {
            Log.d(TAG, "Unable to reach activity manager", e);
        }
    }

    protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            int action = event.getAction() & MotionEvent.ACTION_MASK;

            // Handle Document switcher
            // Additional optimization when we have software system buttons - start loading the recent
            // tasks on touch down
            if (action == MotionEvent.ACTION_DOWN) {
                preloadRecents();
            } else if (action == MotionEvent.ACTION_CANCEL) {
                cancelPreloadingRecents();
            } else if (action == MotionEvent.ACTION_UP) {
                if (!v.isPressed()) {
                    cancelPreloadingRecents();
                }
            }

            // Handle custom recents long press
            if (action == MotionEvent.ACTION_CANCEL ||
                action == MotionEvent.ACTION_UP) {
                cleanupCustomRecentsLongPressHandler();
            }
            return false;
        }
    };

    /**
     * If a custom Recents Long Press activity was dispatched, then the certain
     * handlers need to be cleaned up after the event ends.
     */
    private void cleanupCustomRecentsLongPressHandler() {
        if (mCustomRecentsLongPressed) {
            mNavigationBarView.setSlippery(false);
            mNavigationBarView.enableSearchBar();
        }
        mCustomRecentsLongPressed = false;
    }

    /**
     * An ACTION_RECENTS_LONG_PRESS intent was received, and a custom handler is
     * set and points to a valid app.  Start this activity.
     */
    private void startCustomRecentsLongPressActivity(ComponentName customComponentName) {
        Intent intent = new Intent(Intent.ACTION_RECENTS_LONG_PRESS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);

        // Include the package name of the app currently in the foreground
        IActivityManager am = ActivityManagerNative.getDefault();
        List<ActivityManager.RecentTaskInfo> recentTasks = null;
        try {
            recentTasks = am.getRecentTasks(
                    1, ActivityManager.RECENT_WITH_EXCLUDED, UserHandle.myUserId());
        } catch (RemoteException e) {
            Log.e(TAG, "Cannot get recent tasks", e);
        }
        if (recentTasks != null && recentTasks.size() > 0) {
            String pkgName = recentTasks.get(0).baseIntent.getComponent().getPackageName();
            intent.putExtra(Intent.EXTRA_CURRENT_PACKAGE_NAME, pkgName);
        }

        intent.setComponent(customComponentName);
        try {
            // Allow the touch event to continue into the new activity.
            mNavigationBarView.setSlippery(true);
            mNavigationBarView.disableSearchBar();
            mCustomRecentsLongPressed = true;

            mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));

        } catch (ActivityNotFoundException e) {
            Log.e(TAG, "Cannot start activity", e);

            // If the activity failed to launch, clean up
            cleanupCustomRecentsLongPressHandler();
        }
    }

    /**
     * Get component name for the recent long press setting. Null means default switch to last app.
     *
     * Note: every time packages changed, the setting must be re-evaluated.  This is to check that the
     * component was not uninstalled or disabled.
     */
    private void updateCustomRecentsLongPressHandler(boolean scanPackages) {
        // scanPackages should be true when the PhoneStatusBar is starting for
        // the first time, and when any package activity occurred.
        if (scanPackages) {
            updateCustomRecentsLongPressCandidates();
        }

        String componentString = Settings.Secure.getString(mContext.getContentResolver(),
                Settings.Secure.RECENTS_LONG_PRESS_ACTIVITY);
        if (componentString == null) {
            mCustomRecentsLongPressHandler = null;
            return;
        }

        ComponentName customComponentName = ComponentName.unflattenFromString(componentString);
        synchronized (mCustomRecentsLongPressHandlerCandidates) {
            for (ComponentName candidate : mCustomRecentsLongPressHandlerCandidates) {
                if (candidate.equals(customComponentName)) {
                    // Found match
                    mCustomRecentsLongPressHandler = customComponentName;

                    return;
                }
            }

            // Did not find match, probably because the selected application has
            // now been uninstalled for some reason. Since user-selected app is
            // still saved inside Settings, PhoneStatusBar should fall back to
            // the default for now.  (We will update this either when the
            // package is reinstalled or when the user selects another Setting.)
            mCustomRecentsLongPressHandler = null;
        }
    }

    /**
     * Updates the cache of Recents Long Press applications.
     *
     * These applications must:
     * - handle the Intent.ACTION_RECENTS_LONG_PRESS (which is permissions protected); and
     * - not be disabled by the user or the system.
     *
     * More than one handler can be a candidate.  When the action is invoked,
     * the user setting (stored in Settings.Secure) is consulted.
     */
    private void updateCustomRecentsLongPressCandidates() {
        synchronized (mCustomRecentsLongPressHandlerCandidates) {
            mCustomRecentsLongPressHandlerCandidates.clear();

            PackageManager pm = mContext.getPackageManager();
            Intent intent = new Intent(Intent.ACTION_RECENTS_LONG_PRESS);

            // Search for all apps that can handle ACTION_RECENTS_LONG_PRESS
            List<ResolveInfo> activities = pm.queryIntentActivities(intent,
                    PackageManager.MATCH_DEFAULT_ONLY);
            for (ResolveInfo info : activities) {
                // Only cache packages that are not disabled
                int packageState = mContext.getPackageManager().getApplicationEnabledSetting(
                        info.activityInfo.packageName);

                if (packageState != PackageManager.COMPONENT_ENABLED_STATE_DISABLED &&
                    packageState != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {

                    mCustomRecentsLongPressHandlerCandidates.add(
                        new ComponentName(info.activityInfo.packageName, info.activityInfo.name));
                }

            }
        }
    }

    private ActivityManager.RunningTaskInfo getLastTask(final ActivityManager am) {
        final String defaultHomePackage = resolveCurrentLauncherPackage();
        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(5);