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

Commit 720e3aee authored by Roman Birg's avatar Roman Birg Committed by Steve Kondik
Browse files

SystemUI: make long pressing recent switch to last app



With this patch, long pressing the recents key on the navigation bar
will attempt to switch to your last application in the activity stack
before the active one.

The user's current home launcher package is ignored as well as SystemUI (so that
RecentsActivity does not influence the behavior).

Change-Id: I38771e0fce16b55bb5186af47115b4e812cb60b0
Signed-off-by: default avatarRoman Birg <roman@cyngn.com>
Signed-off-by: default avatarAdnan Begovic <adnan@cyngn.com>

frameworks: allow last app action to be assigned to hardware keys

Also fix multi-user handling for 'switch to last app' and 'kill
foreground app' actions.

Change-Id: I1fb586d19b1198e6888b0f11e0a2813acf0bcd5b
Signed-off-by: default avatarRoman Birg <roman@cyngn.com>
Signed-off-by: default avatarDanny Baumann <dannybaumann@web.de>
Signed-off-by: default avatarAdnan Begovic <adnan@cyngn.com>

make switch to last app animation snappier

Change-Id: Ia253a3db45109da7dd4f7a2467184d81c04918b4
parent 2d40bb13
Loading
Loading
Loading
Loading
+149 −0
Original line number Diff line number Diff line
package com.android.internal.util.cm;

import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.IActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;

import android.view.HapticFeedbackConstants;
import android.widget.Toast;
import com.android.internal.R;

import java.util.List;

public class ActionUtils {
    private static final boolean DEBUG = false;
    private static final String TAG = ActionUtils.class.getSimpleName();
    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";

    /**
     * Kills the top most / most recent user application, but leaves out the launcher.
     * This is function governed by {@link Settings.Secure.KILL_APP_LONGPRESS_BACK}.
     *
     * @param context the current context, used to retrieve the package manager.
     * @param userId the ID of the currently active user
     * @return {@code true} when a user application was found and closed.
     */
    public static boolean killForegroundApp(Context context, int userId) {
        try {
            return killForegroundAppInternal(context, userId);
        } catch (RemoteException e) {
            Log.e(TAG, "Could not kill foreground app");
        }
        return false;
    }

    private static boolean killForegroundAppInternal(Context context, int userId)
            throws RemoteException {
        try {
            final Intent intent = new Intent(Intent.ACTION_MAIN);
            String defaultHomePackage = "com.android.launcher";
            intent.addCategory(Intent.CATEGORY_HOME);
            final ResolveInfo res = context.getPackageManager().resolveActivity(intent, 0);

            if (res.activityInfo != null && !res.activityInfo.packageName.equals("android")) {
                defaultHomePackage = res.activityInfo.packageName;
            }

            IActivityManager am = ActivityManagerNative.getDefault();
            List<ActivityManager.RunningAppProcessInfo> apps = am.getRunningAppProcesses();
            for (ActivityManager.RunningAppProcessInfo appInfo : apps) {
                int uid = appInfo.uid;
                // Make sure it's a foreground user application (not system,
                // root, phone, etc.)
                if (uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID
                        && appInfo.importance ==
                        ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                    if (appInfo.pkgList != null && (appInfo.pkgList.length > 0)) {
                        for (String pkg : appInfo.pkgList) {
                            if (!pkg.equals("com.android.systemui")
                                    && !pkg.equals(defaultHomePackage)) {
                                am.forceStopPackage(pkg, UserHandle.USER_CURRENT);
                                return true;
                            }
                        }
                    } else {
                        Process.killProcess(appInfo.pid);
                        return true;
                    }
                }
            }
        } catch (RemoteException remoteException) {
            // Do nothing; just let it go.
        }
        return false;
    }

    /**
     * Attempt to bring up the last activity in the stack before the current active one.
     *
     * @param context
     * @return whether an activity was found to switch to
     */
    public static boolean switchToLastApp(Context context, int userId) {
        try {
            return switchToLastAppInternal(context, userId);
        } catch (RemoteException e) {
            Log.e(TAG, "Could not switch to last app");
        }
        return false;
    }

    private static boolean switchToLastAppInternal(Context context, int userId)
            throws RemoteException {
        ActivityManager.RecentTaskInfo lastTask = getLastTask(context, userId);

        if (lastTask == null || lastTask.id < 0) {
            return false;
        }

        final String packageName = lastTask.baseIntent.getComponent().getPackageName();
        final IActivityManager am = ActivityManagerNative.getDefault();
        final ActivityOptions opts = ActivityOptions.makeCustomAnimation(context,
                com.android.internal.R.anim.last_app_in,
                com.android.internal.R.anim.last_app_out);

        if (DEBUG) Log.d(TAG, "switching to " + packageName);
        am.moveTaskToFront(lastTask.id, ActivityManager.MOVE_TASK_NO_USER_ACTION, opts.toBundle());

        return true;
    }

    private static ActivityManager.RecentTaskInfo getLastTask(Context context, int userId)
            throws RemoteException {
        final String defaultHomePackage = resolveCurrentLauncherPackage(context, userId);
        final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        final List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(5,
                ActivityManager.RECENT_IGNORE_UNAVAILABLE, userId);

        for (int i = 1; i < tasks.size(); i++) {
            ActivityManager.RecentTaskInfo task = tasks.get(i);
            if (task.origActivity != null) {
                task.baseIntent.setComponent(task.origActivity);
            }
            String packageName = task.baseIntent.getComponent().getPackageName();
            if (!packageName.equals(defaultHomePackage)
                    && !packageName.equals(SYSTEMUI_PACKAGE)) {
                return tasks.get(i);
            }
        }

        return null;
    }

    private static String resolveCurrentLauncherPackage(Context context, int userId) {
        final Intent launcherIntent = new Intent(Intent.ACTION_MAIN)
                .addCategory(Intent.CATEGORY_HOME);
        final PackageManager pm = context.getPackageManager();
        final ResolveInfo launcherInfo = pm.resolveActivityAsUser(launcherIntent, 0, userId);
        return launcherInfo.activityInfo.packageName;
    }
}
+41 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The CyanogenMod Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android"
   >

    <translate android:fromXDelta="0%" android:toXDelta="-35%"
        android:zAdjustment="bottom"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/fast_out_linear_in"
        />
    <scale android:fromXScale="0.80" android:toXScale="1.0"
        android:fromYScale="0.80" android:toYScale="1.0"
        android:pivotX="50%" android:pivotY="50%"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/linear"
        />
    <translate android:fromXDelta="-35%" android:toXDelta="35%"
        android:zAdjustment="top"
        android:startOffset="@android:integer/config_shortAnimTime"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/linear_out_slow_in"
        />
    <alpha android:fromAlpha="0.6" android:toAlpha="1.0"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/linear"
        />
</set>
+41 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The CyanogenMod Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<set xmlns:android="http://schemas.android.com/apk/res/android"
  >

    <translate android:fromXDelta="-35%" android:toXDelta="35%"
        android:zAdjustment="top"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/fast_out_linear_in"
        />
    <scale android:fromXScale="1.0" android:toXScale="0.80"
        android:fromYScale="1.0" android:toYScale="0.80"
        android:pivotX="50%" android:pivotY="50%"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/linear"
        />
    <translate android:fromXDelta="35%" android:toXDelta="-35%"
        android:zAdjustment="bottom"
        android:startOffset="@android:integer/config_shortAnimTime"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/linear_out_slow_in"
        />
    <alpha android:fromAlpha="1.0" android:toAlpha="0.6"
        android:duration="@android:integer/config_shortAnimTime"
        android:interpolator="@android:interpolator/linear"
        />
</set>
+5 −0
Original line number Diff line number Diff line
@@ -2712,4 +2712,9 @@
  <java-symbol type="bool" name="config_emergencyCallOnPowerkeyTapGestureEnabled" />
  <java-symbol type="bool" name="config_usb_data_unlock" />
  <java-symbol type="bool" name="config_volte_preferred" />

  <!-- Last app switch animations -->
  <java-symbol type="anim" name="last_app_in" />
  <java-symbol type="anim" name="last_app_out" />

</resources>
+8 −43
Original line number Diff line number Diff line
@@ -42,9 +42,7 @@ import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.SleepToken;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.AppOpsManager;
import android.app.IUiModeManager;
import android.app.ProgressDialog;
@@ -111,9 +109,6 @@ import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.speech.RecognizerIntent;
import android.telecom.TelecomManager;

import cyanogenmod.hardware.CMHardwareManager;
import cyanogenmod.providers.CMSettings;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -173,6 +168,7 @@ import java.util.HashSet;
import java.util.List;
import java.lang.reflect.Constructor;

import cyanogenmod.hardware.CMHardwareManager;
import cyanogenmod.providers.CMSettings;
import dalvik.system.PathClassLoader;

@@ -263,6 +259,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
    private static final int KEY_ACTION_IN_APP_SEARCH = 5;
    private static final int KEY_ACTION_LAUNCH_CAMERA = 6;
    private static final int KEY_ACTION_SLEEP = 7;
    private static final int KEY_ACTION_LAST_APP = 8;

    // Masks for checking presence of hardware keys.
    // Must match values in core/res/res/values/config.xml
@@ -1561,44 +1558,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            if (unpinActivity(false)) {
                return;
            }
            try {
                final Intent intent = new Intent(Intent.ACTION_MAIN);
                String defaultHomePackage = "com.android.launcher";
                intent.addCategory(Intent.CATEGORY_HOME);
                final ResolveInfo res = mContext.getPackageManager().resolveActivity(intent, 0);
                if (res.activityInfo != null && !res.activityInfo.packageName.equals("android")) {
                    defaultHomePackage = res.activityInfo.packageName;
                }
                boolean targetKilled = false;
                IActivityManager am = ActivityManagerNative.getDefault();
                List<RunningAppProcessInfo> apps = am.getRunningAppProcesses();
                for (RunningAppProcessInfo appInfo : apps) {
                    int uid = appInfo.uid;
                    // Make sure it's a foreground user application (not system,
                    // root, phone, etc.)
                    if (uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID
                            && appInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                        if (appInfo.pkgList != null && (appInfo.pkgList.length > 0)) {
                            for (String pkg : appInfo.pkgList) {
                                if (!pkg.equals("com.android.systemui") && !pkg.equals(defaultHomePackage)) {
                                    am.forceStopPackage(pkg, UserHandle.USER_CURRENT);
                                    targetKilled = true;
                                    break;
                                }
                            }
                        } else {
                            Process.killProcess(appInfo.pid);
                            targetKilled = true;
                        }
                    }
                    if (targetKilled) {
            if (ActionUtils.killForegroundApp(mContext, mCurrentUserId)) {
                performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
                Toast.makeText(mContext, R.string.app_killed_message, Toast.LENGTH_SHORT).show();
                        break;
                    }
                }
            } catch (RemoteException remoteException) {
                // Do nothing; just let it go.
            }
        }
    };
@@ -1741,6 +1703,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            case KEY_ACTION_SLEEP:
                mPowerManager.goToSleep(SystemClock.uptimeMillis());
                break;
            case KEY_ACTION_LAST_APP:
                ActionUtils.switchToLastApp(mContext, mCurrentUserId);
                break;
            default:
                break;
         }