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

Commit 5e56e0c0 authored by Jacqueline Bronger's avatar Jacqueline Bronger
Browse files

Handle all TvPipActions within one component.

Introduces a TvPipActionsProvider that keeps the list of all the pip
actions that should be displayed. It gets updates about the PiP from the
TvPipController and sends changes to the actions to its listeners
(TvPipMenuView and TvPipNotificationController).

Bug: 258653494
Test: atest TvPipActionProviderTest
Test: manual - check PiP menu content is populated

Change-Id: Ibf892d7d3fb3efc1182faabca68b9ef772c9c606
parent bc2095c8
Loading
Loading
Loading
Loading
+15 −5
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.tv.TvPipActionsProvider;
import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
import com.android.wm.shell.pip.tv.TvPipBoundsController;
import com.android.wm.shell.pip.tv.TvPipBoundsState;
@@ -75,6 +76,7 @@ public abstract class TvPipModule {
            PipTaskOrganizer pipTaskOrganizer,
            TvPipMenuController tvPipMenuController,
            PipMediaController pipMediaController,
            TvPipActionsProvider tvPipActionsProvider,
            PipTransitionController pipTransitionController,
            TvPipNotificationController tvPipNotificationController,
            TaskStackListenerImpl taskStackListener,
@@ -95,6 +97,7 @@ public abstract class TvPipModule {
                        pipTransitionController,
                        tvPipMenuController,
                        pipMediaController,
                        tvPipActionsProvider,
                        tvPipNotificationController,
                        taskStackListener,
                        pipParamsChangedForwarder,
@@ -157,10 +160,10 @@ public abstract class TvPipModule {
            Context context,
            TvPipBoundsState tvPipBoundsState,
            SystemWindows systemWindows,
            PipMediaController pipMediaController,
            TvPipActionsProvider tvPipActionsProvider,
            @ShellMainThread Handler mainHandler) {
        return new TvPipMenuController(context, tvPipBoundsState, systemWindows, pipMediaController,
                mainHandler);
        return new TvPipMenuController(context, tvPipBoundsState, systemWindows, mainHandler,
                tvPipActionsProvider);
    }

    // Handler needed for registerReceiverForAllUsers()
@@ -169,10 +172,10 @@ public abstract class TvPipModule {
    static TvPipNotificationController provideTvPipNotificationController(Context context,
            PipMediaController pipMediaController,
            PipParamsChangedForwarder pipParamsChangedForwarder,
            TvPipBoundsState tvPipBoundsState,
            TvPipActionsProvider tvPipActionsProvider,
            @ShellMainThread Handler mainHandler) {
        return new TvPipNotificationController(context, pipMediaController,
                pipParamsChangedForwarder, tvPipBoundsState, mainHandler);
                pipParamsChangedForwarder, tvPipActionsProvider, mainHandler);
    }

    @WMSingleton
@@ -224,4 +227,11 @@ public abstract class TvPipModule {
            @ShellMainThread ShellExecutor mainExecutor) {
        return new PipAppOpsListener(context, pipTaskOrganizer::removePip, mainExecutor);
    }

    @WMSingleton
    @Provides
    static TvPipActionsProvider provideTvPipActionsProvider(Context context,
            PipMediaController pipMediaController) {
        return new TvPipActionsProvider(context, pipMediaController);
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -18,7 +18,9 @@ package com.android.wm.shell.pip.tv;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Handler;

import com.android.internal.protolog.common.ProtoLog;
@@ -81,4 +83,6 @@ abstract class TvPipAction {
        }
    }

    abstract Notification.Action toNotificationAction(Context context);

}
+232 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source 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.
 */

package com.android.wm.shell.pip.tv;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
import static com.android.wm.shell.pip.tv.TvPipNotificationController.ACTION_CLOSE_PIP;
import static com.android.wm.shell.pip.tv.TvPipNotificationController.ACTION_MOVE_PIP;
import static com.android.wm.shell.pip.tv.TvPipNotificationController.ACTION_TOGGLE_EXPANDED_PIP;
import static com.android.wm.shell.pip.tv.TvPipNotificationController.ACTION_TO_FULLSCREEN;

import android.annotation.NonNull;
import android.app.RemoteAction;
import android.content.Context;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * Creates the system TvPipActions (fullscreen, close, move, expand/collapse),  and handles all the
 * changes to the actions, including the custom app actions and media actions. Other components can
 * listen to those changes.
 */
public class TvPipActionsProvider {
    private static final String TAG = TvPipActionsProvider.class.getSimpleName();

    private static final int CLOSE_ACTION_INDEX = 1;
    private static final int FIRST_CUSTOM_ACTION_INDEX = 2;

    private final List<Listener> mListeners = new ArrayList<>();

    private final List<TvPipAction> mActionsList;
    private final TvPipSystemAction mDefaultCloseAction;
    private final TvPipSystemAction mExpandCollapseAction;

    private final List<RemoteAction> mMediaActions = new ArrayList<>();
    private final List<RemoteAction> mAppActions = new ArrayList<>();

    public TvPipActionsProvider(Context context, PipMediaController pipMediaController) {

        mActionsList = new ArrayList<>();
        mActionsList.add(new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
                R.drawable.pip_ic_fullscreen_white, ACTION_TO_FULLSCREEN, context));

        mDefaultCloseAction = new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
                R.drawable.pip_ic_close_white, ACTION_CLOSE_PIP, context);
        mActionsList.add(mDefaultCloseAction);

        mActionsList.add(new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
                R.drawable.pip_ic_move_white, ACTION_MOVE_PIP, context));

        mExpandCollapseAction = new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
                R.drawable.pip_ic_collapse, ACTION_TOGGLE_EXPANDED_PIP, context);
        mActionsList.add(mExpandCollapseAction);

        pipMediaController.addActionListener(this::onMediaActionsChanged);
    }

    private void notifyActionsChanged(int added, int changed, int startIndex) {
        for (Listener listener : mListeners) {
            listener.onActionsChanged(added, changed, startIndex);
        }
    }

    @VisibleForTesting(visibility = PACKAGE)
    public void setAppActions(@NonNull List<RemoteAction> appActions, RemoteAction closeAction) {
        // Update close action.
        mActionsList.set(CLOSE_ACTION_INDEX,
                closeAction == null ? mDefaultCloseAction
                        : new TvPipCustomAction(ACTION_CUSTOM_CLOSE, closeAction));
        notifyActionsChanged(/* added= */ 0, /* updated= */ 1, CLOSE_ACTION_INDEX);

        // Replace custom actions with new ones.
        mAppActions.clear();
        for (RemoteAction action : appActions) {
            if (action != null && !PipUtils.remoteActionsMatch(action, closeAction)) {
                // Only show actions that aren't duplicates of the custom close action.
                mAppActions.add(action);
            }
        }

        updateCustomActions(mAppActions);
    }

    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    public void onMediaActionsChanged(List<RemoteAction> actions) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: onMediaActionsChanged()", TAG);

        mMediaActions.clear();
        // Don't show disabled actions.
        for (RemoteAction remoteAction : actions) {
            if (remoteAction.isEnabled()) {
                mMediaActions.add(remoteAction);
            }
        }

        updateCustomActions(mMediaActions);
    }

    private void updateCustomActions(@NonNull List<RemoteAction> customActions) {
        List<RemoteAction> newCustomActions = customActions;
        if (newCustomActions == mMediaActions && !mAppActions.isEmpty()) {
            // Don't show the media actions while there are app actions.
            return;
        } else if (newCustomActions == mAppActions && mAppActions.isEmpty()) {
            // If all the app actions were removed, show the media actions.
            newCustomActions = mMediaActions;
        }

        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: replaceCustomActions, count: %d", TAG, newCustomActions.size());
        int oldCustomActionsCount = 0;
        for (TvPipAction action : mActionsList) {
            if (action.getActionType() == ACTION_CUSTOM) {
                oldCustomActionsCount++;
            }
        }
        mActionsList.removeIf(tvPipAction -> tvPipAction.getActionType() == ACTION_CUSTOM);

        List<TvPipAction> actions = new ArrayList<>();
        for (RemoteAction action : newCustomActions) {
            actions.add(new TvPipCustomAction(ACTION_CUSTOM, action));
        }
        mActionsList.addAll(FIRST_CUSTOM_ACTION_INDEX, actions);

        int added = newCustomActions.size() - oldCustomActionsCount;
        int changed = Math.min(newCustomActions.size(), oldCustomActionsCount);
        notifyActionsChanged(added, changed, FIRST_CUSTOM_ACTION_INDEX);
    }

    @VisibleForTesting(visibility = PACKAGE)
    public void updateExpansionEnabled(boolean enabled) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: updateExpansionState, enabled: %b", TAG, enabled);
        int actionIndex = mActionsList.indexOf(mExpandCollapseAction);
        boolean actionInList = actionIndex != -1;
        if (enabled && !actionInList) {
            mActionsList.add(mExpandCollapseAction);
            actionIndex = mActionsList.size() - 1;
        } else if (!enabled && actionInList) {
            mActionsList.remove(actionIndex);
        } else {
            return;
        }
        notifyActionsChanged(/* added= */ enabled ? 1 : -1, /* updated= */ 0, actionIndex);
    }

    @VisibleForTesting(visibility = PACKAGE)
    public void onPipExpansionToggled(boolean expanded) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: onPipExpansionToggled, expanded: %b", TAG, expanded);

        mExpandCollapseAction.update(
                expanded ? R.string.pip_collapse : R.string.pip_expand,
                expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);

        notifyActionsChanged(/* added= */ 0, /* updated= */ 1,
                mActionsList.indexOf(mExpandCollapseAction));
    }

    List<TvPipAction> getActionsList() {
        return mActionsList;
    }

    @NonNull
    TvPipAction getCloseAction() {
        return mActionsList.get(CLOSE_ACTION_INDEX);
    }

    void addListener(Listener listener) {
        if (!mListeners.contains(listener)) {
            mListeners.add(listener);
        }
    }

    /**
     * Returns the index of the first action of the given action type or -1 if none can be found.
     */
    int getFirstIndexOfAction(@TvPipAction.ActionType int actionType) {
        for (int i = 0; i < mActionsList.size(); i++) {
            if (mActionsList.get(i).getActionType() == actionType) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Allow components to listen to updates to the actions list, including where they happen so
     * that changes can be animated.
     */
    interface Listener {
        /**
         * Notifies the listener how many actions were added/removed or updated.
         *
         * @param added      can be positive (number of actions added), negative (number of actions
         *                   removed) or zero (the number of actions stayed the same).
         * @param updated    the number of actions that might have been updated and need to be
         *                   refreshed.
         * @param startIndex The index of the first updated action. The added/removed actions start
         *                   at (startIndex + updated).
         */
        void onActionsChanged(int added, int updated, int startIndex);
    }
}
+21 −5
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
    private final PipAppOpsListener mAppOpsListener;
    private final PipTaskOrganizer mPipTaskOrganizer;
    private final PipMediaController mPipMediaController;
    private final TvPipActionsProvider mTvPipActionsProvider;
    private final TvPipNotificationController mPipNotificationController;
    private final TvPipMenuController mTvPipMenuController;
    private final PipTransitionController mPipTransitionController;
@@ -141,6 +142,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
            PipTransitionController pipTransitionController,
            TvPipMenuController tvPipMenuController,
            PipMediaController pipMediaController,
            TvPipActionsProvider tvPipActionsProvider,
            TvPipNotificationController pipNotificationController,
            TaskStackListenerImpl taskStackListener,
            PipParamsChangedForwarder pipParamsChangedForwarder,
@@ -159,6 +161,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
                pipTransitionController,
                tvPipMenuController,
                pipMediaController,
                tvPipActionsProvider,
                pipNotificationController,
                taskStackListener,
                pipParamsChangedForwarder,
@@ -179,6 +182,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
            PipTransitionController pipTransitionController,
            TvPipMenuController tvPipMenuController,
            PipMediaController pipMediaController,
            TvPipActionsProvider tvPipActionsProvider,
            TvPipNotificationController pipNotificationController,
            TaskStackListenerImpl taskStackListener,
            PipParamsChangedForwarder pipParamsChangedForwarder,
@@ -198,6 +202,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
        mTvPipBoundsController.setListener(this);

        mPipMediaController = pipMediaController;
        mTvPipActionsProvider = tvPipActionsProvider;

        mPipNotificationController = pipNotificationController;
        mPipNotificationController.setDelegate(this);
@@ -241,7 +246,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
                "%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));

        loadConfigurations();
        mPipNotificationController.onConfigurationChanged(mContext);
        mPipNotificationController.onConfigurationChanged();
        mTvPipBoundsAlgorithm.onConfigurationChanged(mContext);
    }

@@ -310,7 +315,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
        }
        mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding);
        mTvPipBoundsState.setTvPipExpanded(expanding);
        mPipNotificationController.updateExpansionState();

        updatePinnedStackBounds();
    }
@@ -373,7 +377,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
    @Override
    public void onPipTargetBoundsChange(Rect targetBounds, int animationDuration) {
        mPipTaskOrganizer.scheduleAnimateResizePip(targetBounds,
                animationDuration, rect -> mTvPipMenuController.updateExpansionState());
                animationDuration, null);
        mTvPipMenuController.onPipTransitionToTargetBoundsStarted(targetBounds);
    }

@@ -454,6 +458,11 @@ public class TvPipController implements PipTransitionController.PipTransitionCal

    @Override
    public void onPipTransitionStarted(int direction, Rect currentPipBounds) {
        final boolean enterPipTransition = PipAnimationController.isInPipDirection(direction);
        if (enterPipTransition && mState == STATE_NO_PIP) {
            // Set the initial ability to expand the PiP when entering PiP.
            updateExpansionState();
        }
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: onPipTransition_Started(), state=%s, direction=%d",
                TAG, stateToName(mState), direction);
@@ -465,6 +474,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
                "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState));
        mTvPipMenuController.onPipTransitionFinished(
                PipAnimationController.isInPipDirection(direction));
        mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
    }

    @Override
@@ -477,6 +487,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
                "%s: onPipTransition_Finished(), state=%s, direction=%d",
                TAG, stateToName(mState), direction);
        mTvPipMenuController.onPipTransitionFinished(enterPipTransition);
        mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded());
    }

    private void updateExpansionState() {
        mTvPipActionsProvider.updateExpansionEnabled(mTvPipBoundsState.isTvExpandedPipSupported()
                && mTvPipBoundsState.getDesiredTvExpandedAspectRatio() != 0);
    }

    private void setState(@State int state) {
@@ -534,7 +550,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
                ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                        "%s: onActionsChanged()", TAG);

                mTvPipMenuController.setAppActions(actions, closeAction);
                mTvPipActionsProvider.setAppActions(actions, closeAction);
                mCloseAction = closeAction;
            }

@@ -555,7 +571,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
                        "%s: onExpandedAspectRatioChanged: %f", TAG, ratio);

                mTvPipBoundsState.setDesiredTvExpandedAspectRatio(ratio, false);
                mTvPipMenuController.updateExpansionState();
                updateExpansionState();

                // 1) PiP is expanded and only aspect ratio changed, but wasn't disabled
                // --> update bounds, but don't toggle
+23 −0
Original line number Diff line number Diff line
@@ -16,9 +16,15 @@

package com.android.wm.shell.pip.tv;

import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE;
import static android.app.Notification.Action.SEMANTIC_ACTION_NONE;

import android.annotation.NonNull;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;

import com.android.wm.shell.common.TvWindowMenuActionButton;
@@ -56,4 +62,21 @@ public class TvPipCustomAction extends TvPipAction {
        return mRemoteAction.getActionIntent();
    }

    @Override
    Notification.Action toNotificationAction(Context context) {
        Notification.Action.Builder builder = new Notification.Action.Builder(
                mRemoteAction.getIcon(),
                mRemoteAction.getTitle(),
                mRemoteAction.getActionIntent());
        Bundle extras = new Bundle();
        extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION,
                mRemoteAction.getContentDescription());
        builder.addExtras(extras);

        builder.setSemanticAction(isCloseAction()
                ? SEMANTIC_ACTION_DELETE : SEMANTIC_ACTION_NONE);
        builder.setContextual(true);
        return builder.build();
    }

}
Loading