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

Commit fa28d939 authored by James Cook's avatar James Cook
Browse files

Allow more transient icons in app shelf

* Build the icon views dynamically rather than having a fixed number
  in the layout XML file
* Increase the maximum number of icons
* Keep the sort order stable as icons are added
* Infer when icons should be added and removed by observing recent
  tasks.

Bug: 22031100
Change-Id: Ifa78695ab8b332a47ff116bb502d8d095881e3f0
parent 968403dd
Loading
Loading
Loading
Loading
+5 −69
Original line number Diff line number Diff line
@@ -91,42 +91,10 @@
                    android:src="@drawable/nav_app_divider"
                    />
    
                <!-- TODO: Build the list of icons dynamically. -->
                <com.android.systemui.statusbar.phone.NavigationBarRecents
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    >
                    <ImageView android:id="@+id/recent0"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:layout_marginLeft="8dp"
                        android:layout_marginRight="8dp"
                        android:padding="6dp"
                        android:layout_gravity="center"
                        android:scaleType="centerInside"
                        android:visibility="invisible"
                        />
                    <ImageView android:id="@+id/recent1"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:layout_marginLeft="8dp"
                        android:layout_marginRight="8dp"
                        android:padding="6dp"
                        android:layout_gravity="center"
                        android:scaleType="centerInside"
                        android:visibility="invisible"
                        />
                    <ImageView android:id="@+id/recent2"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:layout_marginLeft="8dp"
                        android:layout_marginRight="8dp"
                        android:padding="6dp"
                        android:layout_gravity="center"
                        android:scaleType="centerInside"
                        android:visibility="invisible"
                    />
                </com.android.systemui.statusbar.phone.NavigationBarRecents>
            </LinearLayout>

            <FrameLayout
@@ -287,42 +255,10 @@
                    android:src="@drawable/nav_app_divider"
                    />
    
                <!-- TODO: Build the list of icons dynamically. -->
            <com.android.systemui.statusbar.phone.NavigationBarRecents
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                    >
                    <ImageView android:id="@+id/recent0"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:layout_marginLeft="8dp"
                        android:layout_marginRight="8dp"
                        android:padding="6dp"
                        android:layout_gravity="center"
                        android:scaleType="centerInside"
                        android:visibility="invisible"
                        />
                    <ImageView android:id="@+id/recent1"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:layout_marginLeft="8dp"
                        android:layout_marginRight="8dp"
                        android:padding="6dp"
                        android:layout_gravity="center"
                        android:scaleType="centerInside"
                        android:visibility="invisible"
                        />
                    <ImageView android:id="@+id/recent2"
                        android:layout_width="48dp"
                        android:layout_height="48dp"
                        android:layout_marginLeft="8dp"
                        android:layout_marginRight="8dp"
                        android:padding="6dp"
                        android:layout_gravity="center"
                        android:scaleType="centerInside"
                        android:visibility="invisible"
                />
                </com.android.systemui.statusbar.phone.NavigationBarRecents>
            </LinearLayout>

            <FrameLayout
+19 −11
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -111,19 +111,27 @@ class NavigationBarApps extends LinearLayout {
        return button;
    }

    // Not shared with NavigationBarRecents because the data model is specific to pinned apps.
    private class AppLongClickListener implements View.OnLongClickListener {
        @Override
        public boolean onLongClick(View v) {
            mDragView = v;
            ImageView icon = (ImageView) v;
            ComponentName activityName = mAppsModel.getApp(indexOfChild(v));
            startAppDrag(icon, activityName);
            return true;
        }
    }

    /** Helper function to start dragging an app icon (either pinned or recent). */
    static void startAppDrag(ImageView icon, ComponentName activityName) {
        // The drag data is an Intent to launch the activity.
        Intent mainIntent = Intent.makeMainActivity(activityName);
        ClipData dragData = ClipData.newIntent("", mainIntent);
        // Use the ImageView to create the shadow.
            View.DragShadowBuilder shadow = new AppIconDragShadowBuilder((ImageView) v);
            v.startDrag(dragData, shadow, null /* myLocalState */, 0 /* flags */);
            return true;
        }
        View.DragShadowBuilder shadow = new AppIconDragShadowBuilder(icon);
        // Use a global drag because the icon might be dragged into the launcher.
        icon.startDrag(dragData, shadow, null /* myLocalState */, View.DRAG_FLAG_GLOBAL);
    }

    @Override
@@ -161,7 +169,7 @@ class NavigationBarApps extends LinearLayout {
     * an app shortcut and will be accepted for a drop.
     */
    private boolean onDragStarted(DragEvent event) {
        if (DEBUG) Log.d(TAG, "onDragStarted");
        if (DEBUG) Slog.d(TAG, "onDragStarted");

        // Ensure that an app shortcut is being dragged.
        if (!canAcceptDrag(event)) {
@@ -194,7 +202,7 @@ class NavigationBarApps extends LinearLayout {
     * needs to use LinearLayout/ViewGroup methods.
     */
    private void onDragEnteredIcon(View target) {
        if (DEBUG) Log.d(TAG, "onDragEntered " + indexOfChild(target));
        if (DEBUG) Slog.d(TAG, "onDragEntered " + indexOfChild(target));

        // If the drag didn't start from an existing icon, add an invisible placeholder to create
        // empty space for the user to drag into.
@@ -227,7 +235,7 @@ class NavigationBarApps extends LinearLayout {
    }

    private boolean onDrop(DragEvent event) {
        if (DEBUG) Log.d(TAG, "onDrop");
        if (DEBUG) Slog.d(TAG, "onDrop");

        int dragViewIndex = indexOfChild(mDragView);
        if (mAppsModel.getApp(dragViewIndex) == null) {
@@ -285,7 +293,7 @@ class NavigationBarApps extends LinearLayout {

    /** Cleans up at the end of the drag. */
    private boolean onDragEnded() {
        if (DEBUG) Log.d(TAG, "onDragEnded");
        if (DEBUG) Slog.d(TAG, "onDragEnded");

        if (mDragView != null) {
            // The icon wasn't dropped into the app list. Remove the placeholder.
+85 −49
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.IActivityManager;
import android.app.ITaskStackListener;
import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -28,7 +27,10 @@ import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -36,6 +38,7 @@ import android.widget.LinearLayout;
import com.android.systemui.R;

import java.util.List;
import java.util.Set;

/**
 * Recent task icons appearing in the navigation bar. Touching an icon brings the activity to the
@@ -43,21 +46,31 @@ import java.util.List;
 * icons.
 */
class NavigationBarRecents extends LinearLayout {
    private final static boolean DEBUG = false;
    private final static String TAG = "NavigationBarRecents";

    private final static int[] RECENT_APP_BUTTON_IDS = { R.id.recent0, R.id.recent1, R.id.recent2 };
    // Maximum number of icons to show.
    // TODO: Implement an overflow UI so the shelf can display an unlimited number of recents.
    private final static int MAX_RECENTS = 10;

    private final ActivityManager mActivityManager;
    private final PackageManager mPackageManager;
    private final LayoutInflater mLayoutInflater;
    private final TaskStackListenerImpl mTaskStackListener;
    // Recent tasks being displayed in the shelf.
    private final Set<ComponentName> mCurrentTasks = new ArraySet<ComponentName>(MAX_RECENTS);

    public NavigationBarRecents(Context context, AttributeSet attrs) {
        super(context, attrs);
        mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        mPackageManager = getContext().getPackageManager();
        mLayoutInflater = LayoutInflater.from(context);

        // Listen for task stack changes and refresh when they happen. Update notifications happen
        // on an IPC thread, so use Handler to handle the message on the main thread.
        // TODO: This has too much latency. It only adds the icon when app launch is completed
        // and the launch animation is done playing. This class should add the icon immediately
        // when the launch starts.
        Handler handler = new Handler();
        mTaskStackListener = new TaskStackListenerImpl(handler);
        IActivityManager iam = ActivityManagerNative.getDefault();
@@ -68,54 +81,86 @@ class NavigationBarRecents extends LinearLayout {
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        // Set up the buttons.
        for (int i = 0; i < RECENT_APP_BUTTON_IDS.length; i++) {
            ImageView button = getRecentAppButton(i);
            button.setOnLongClickListener(AppLongClickListener.getInstance());
        }

        // TODO: When is the right time to do the initial update?
        updateRecentApps();
    }

    private ImageView getRecentAppButton(int index) {
        return (ImageView) findViewById(RECENT_APP_BUTTON_IDS[index]);
    }

    private void updateRecentApps() {
        // TODO: Should this be getRunningTasks?
        List<ActivityManager.RecentTaskInfo> tasks = mActivityManager.getRecentTasksForUser(
                RECENT_APP_BUTTON_IDS.length,
        // TODO: Query other UserHandles?
        List<ActivityManager.RecentTaskInfo> recentTasks = mActivityManager.getRecentTasksForUser(
                MAX_RECENTS,
                ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
                ActivityManager.RECENT_IGNORE_UNAVAILABLE |
                ActivityManager.RECENT_INCLUDE_PROFILES |
                ActivityManager.RECENT_WITH_EXCLUDED,
                UserHandle.USER_CURRENT);
        // Show the recent icons with the oldest on the left.
        int buttonIndex = 0;
        int taskIndex = tasks.size() - 1;
        while (taskIndex >= 0 && buttonIndex < RECENT_APP_BUTTON_IDS.length) {
            updateRecentAppButton(getRecentAppButton(buttonIndex), tasks.get(taskIndex));
            taskIndex--;
            buttonIndex++;
        if (DEBUG) Slog.d(TAG, "Got recents " + recentTasks.size());
        removeMissingRecents(recentTasks);
        addNewRecents(recentTasks);
    }

    // Remove any icons that disappeared from recents.
    private void removeMissingRecents(List<ActivityManager.RecentTaskInfo> recentTasks) {
        // Extract the component names.
        Set<ComponentName> recentComponents = new ArraySet<ComponentName>(recentTasks.size());
        for (ActivityManager.RecentTaskInfo task : recentTasks) {
            ComponentName component = task.baseIntent.getComponent();
            if (component == null) {  // It's unclear if this can happen in practice.
                continue;
            }
            recentComponents.add(component);
        }
        // Hide the unused buttons.
        while (buttonIndex < RECENT_APP_BUTTON_IDS.length) {
            hideButton(getRecentAppButton(buttonIndex));
            buttonIndex++;

        // Start with a copy of the currently displayed tasks.
        Set<ComponentName> removed = new ArraySet<ComponentName>(mCurrentTasks);
        // Remove all the entries that still exist in recents.
        removed.removeAll(recentComponents);
        // The remaining entries no longer exist in recents, so remove their icons.
        for (ComponentName task : removed) {
            removeIcon(task);
        }
    }

    private void updateRecentAppButton(ImageView button, ActivityManager.RecentTaskInfo task) {
        ComponentName component = task.baseIntent.getComponent();
        if (component == null) {
            hideButton(button);
    // Removes the icon for a task.
    private void removeIcon(ComponentName task) {
        for (int i = 0; i < getChildCount(); i++) {
            ComponentName childTask = (ComponentName) getChildAt(i).getTag();
            if (childTask.equals(task)) {
                if (DEBUG) Slog.d(TAG, "removing missing " + task);
                removeViewAt(i);
                mCurrentTasks.remove(task);
                return;
            }
        }
    }

    // Adds new tasks at the end of the icon list.
    private void addNewRecents(List<ActivityManager.RecentTaskInfo> recentTasks) {
        for (ActivityManager.RecentTaskInfo task : recentTasks) {
            // Don't overflow the list.
            if (getChildCount() >= MAX_RECENTS) {
                return;
            }
            ComponentName component = task.baseIntent.getComponent();
            if (component == null) {  // It's unclear if this can happen in practice.
                continue;
            }
            // Don't add tasks that are already being shown.
            if (mCurrentTasks.contains(component)) {
                continue;
            }
            addRecentAppButton(task);
        }
    }

    // Adds an icon at the end of the list to represent an activity for a given component.
    private void addRecentAppButton(ActivityManager.RecentTaskInfo task) {
        // Add this task to the currently-shown set.
        ComponentName component = task.baseIntent.getComponent();
        mCurrentTasks.add(component);
        if (DEBUG) Slog.d(TAG, "adding " + component);

        ImageView button = (ImageView) mLayoutInflater.inflate(
                R.layout.navigation_bar_app_item, this, false /* attachToRoot */);
        button.setOnLongClickListener(AppLongClickListener.getInstance());
        addView(button);

        // Use the View's tag to store metadata for drag and drop.
        button.setTag(component);
@@ -134,11 +179,6 @@ class NavigationBarRecents extends LinearLayout {
        });
    }

    private void hideButton(ImageView button) {
        button.setImageDrawable(null);
        button.setVisibility(View.GONE);
    }

    /**
     * A listener that updates the app buttons whenever the recents task stack changes.
     * NOTE: This is not the right way to do this.
@@ -173,13 +213,9 @@ class NavigationBarRecents extends LinearLayout {

        @Override
        public boolean onLongClick(View v) {
            ImageView icon = (ImageView) v;
            ComponentName activityName = (ComponentName) v.getTag();
            // The drag data is an Intent to launch the activity.
            Intent mainIntent = Intent.makeMainActivity(activityName);
            ClipData dragData = ClipData.newIntent("", mainIntent);
            // Use the ImageView to create the shadow.
            View.DragShadowBuilder shadow = new AppIconDragShadowBuilder((ImageView) v);
            v.startDrag(dragData, shadow, null /* myLocalState */, 0 /* flags */);
            NavigationBarApps.startAppDrag(icon, activityName);
            return true;
        }
    }
+1 −1
Original line number Diff line number Diff line
@@ -391,7 +391,7 @@ public final class ActivityManagerService extends ActivityManagerNative
    static final int LAST_PREBOOT_DELIVERED_FILE_VERSION = 10000;
    // Delay in notifying task stack change listeners (in millis)
    static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 1000;
    static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
    // Necessary ApplicationInfo flags to mark an app as persistent
    private static final int PERSISTENT_MASK =