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

Commit a556fe6a authored by Winson Chung's avatar Winson Chung
Browse files

Removing PIP notification for menu action

Bug: 65482444
Test: Launch PiP, tap to show menu, tap settings to go to settings
Change-Id: I204e18ffa0b63acbfef899bda91f5da9daf9f0a5
parent 107f7cc4
Loading
Loading
Loading
Loading
+19 −9
Original line number Diff line number Diff line
@@ -60,6 +60,16 @@
      </FrameLayout>
  </FrameLayout>

    <ImageView
        android:id="@+id/settings"
        android:layout_width="@dimen/pip_action_size"
        android:layout_height="@dimen/pip_action_size"
        android:layout_gravity="top|start"
        android:padding="@dimen/pip_action_padding"
        android:contentDescription="@string/pip_phone_settings"
        android:src="@drawable/ic_settings"
        android:background="?android:selectableItemBackgroundBorderless" />

    <ImageView
        android:id="@+id/dismiss"
        android:layout_width="@dimen/pip_action_size"
+3 −0
Original line number Diff line number Diff line
@@ -1887,6 +1887,9 @@
    <!-- Label for PIP close button [CHAR LIMIT=NONE]-->
    <string name="pip_phone_close">Close</string>

    <!-- Label for PIP settings button [CHAR LIMIT=NONE]-->
    <string name="pip_phone_settings">Settings</string>

    <!-- Label for PIP the drag to dismiss hint [CHAR LIMIT=NONE]-->
    <string name="pip_phone_dismiss_hint">Drag down to dismiss</string>

+0 −7
Original line number Diff line number Diff line
@@ -64,7 +64,6 @@ public class PipManager implements BasePipManager {
    private InputConsumerController mInputConsumerController;
    private PipMenuActivityController mMenuController;
    private PipMediaController mMediaController;
    private PipNotificationController mNotificationController;
    private PipTouchHandler mTouchHandler;

    /**
@@ -76,8 +75,6 @@ public class PipManager implements BasePipManager {
            mTouchHandler.onActivityPinned();
            mMediaController.onActivityPinned();
            mMenuController.onActivityPinned();
            mNotificationController.onActivityPinned(packageName, userId,
                    true /* deferUntilAnimationEnds */);

            SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
        }
@@ -90,7 +87,6 @@ public class PipManager implements BasePipManager {
            final int userId = topActivity != null ? topPipActivityInfo.second : 0;
            mMenuController.onActivityUnpinned();
            mTouchHandler.onActivityUnpinned(topActivity);
            mNotificationController.onActivityUnpinned(topActivity, userId);

            SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
        }
@@ -107,7 +103,6 @@ public class PipManager implements BasePipManager {
            mTouchHandler.setTouchEnabled(true);
            mTouchHandler.onPinnedStackAnimationEnded();
            mMenuController.onPinnedStackAnimationEnded();
            mNotificationController.onPinnedStackAnimationEnded();
        }

        @Override
@@ -182,8 +177,6 @@ public class PipManager implements BasePipManager {
                mInputConsumerController);
        mTouchHandler = new PipTouchHandler(context, mActivityManager, mMenuController,
                mInputConsumerController);
        mNotificationController = new PipNotificationController(context, mActivityManager,
                mTouchHandler.getMotionHelper());
        EventBus.getDefault().register(this);
    }

+36 −3
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.systemui.pip.phone;

import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;

import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACTIONS;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ALLOW_TIMEOUT;
import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER;
@@ -39,6 +43,7 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Color;
@@ -46,12 +51,15 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
@@ -105,6 +113,7 @@ public class PipMenuActivity extends Activity {
    private Drawable mBackgroundDrawable;
    private View mMenuContainer;
    private LinearLayout mActionsGroup;
    private View mSettingsButton;
    private View mDismissButton;
    private ImageView mExpandButton;
    private int mBetweenActionPaddingLand;
@@ -218,6 +227,11 @@ public class PipMenuActivity extends Activity {
            }
            return true;
        });
        mSettingsButton = findViewById(R.id.settings);
        mSettingsButton.setAlpha(0);
        mSettingsButton.setOnClickListener((v) -> {
            showSettings();
        });
        mDismissButton = findViewById(R.id.dismiss);
        mDismissButton.setAlpha(0);
        mDismissButton.setOnClickListener((v) -> {
@@ -352,12 +366,14 @@ public class PipMenuActivity extends Activity {
            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                    mMenuContainer.getAlpha(), 1f);
            menuAnim.addUpdateListener(mMenuBgUpdateListener);
            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
                    mSettingsButton.getAlpha(), 1f);
            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                    mDismissButton.getAlpha(), 1f);
            if (menuState == MENU_STATE_FULL) {
                mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
                mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
            } else {
                mMenuContainerAnimator.play(dismissAnim);
                mMenuContainerAnimator.playTogether(settingsAnim, dismissAnim);
            }
            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
            mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
@@ -394,9 +410,11 @@ public class PipMenuActivity extends Activity {
            ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                    mMenuContainer.getAlpha(), 0f);
            menuAnim.addUpdateListener(mMenuBgUpdateListener);
            ObjectAnimator settingsAnim = ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA,
                    mSettingsButton.getAlpha(), 0f);
            ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA,
                    mDismissButton.getAlpha(), 0f);
            mMenuContainerAnimator.playTogether(menuAnim, dismissAnim);
            mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim);
            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
            mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@@ -526,12 +544,14 @@ public class PipMenuActivity extends Activity {
        final float menuAlpha = 1 - fraction;
        if (mMenuState == MENU_STATE_FULL) {
            mMenuContainer.setAlpha(menuAlpha);
            mSettingsButton.setAlpha(menuAlpha);
            mDismissButton.setAlpha(menuAlpha);
            final float interpolatedAlpha =
                    MENU_BACKGROUND_ALPHA * menuAlpha + DISMISS_BACKGROUND_ALPHA * fraction;
            alpha = (int) (interpolatedAlpha * 255);
        } else {
            if (mMenuState == MENU_STATE_CLOSE) {
                mSettingsButton.setAlpha(menuAlpha);
                mDismissButton.setAlpha(menuAlpha);
            }
            alpha = (int) (fraction * DISMISS_BACKGROUND_ALPHA * 255);
@@ -588,6 +608,19 @@ public class PipMenuActivity extends Activity {
        sendMessage(m, "Could not notify controller to show PIP menu");
    }

    private void showSettings() {
        final Pair<ComponentName, Integer> topPipActivityInfo =
                PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
        if (topPipActivityInfo.first != null) {
            final UserHandle user = UserHandle.of(topPipActivityInfo.second);
            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
                    Uri.fromParts("package", topPipActivityInfo.first.getPackageName(), null));
            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
            startActivity(settingsIntent);
        }
    }

    private void notifyActivityCallback(Messenger callback) {
        Message m = Message.obtain();
        m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
+0 −231
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.systemui.pip.phone;

import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS;

import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpChangedListener;
import android.app.IActivityManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.UserHandle;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.Pair;

import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.util.NotificationChannels;

/**
 * Manages the BTW notification that shows whenever an activity enters or leaves picture-in-picture.
 */
public class PipNotificationController {
    private static final String TAG = PipNotificationController.class.getSimpleName();

    private static final String NOTIFICATION_TAG = PipNotificationController.class.getName();
    private static final int NOTIFICATION_ID = 0;

    private Context mContext;
    private IActivityManager mActivityManager;
    private AppOpsManager mAppOpsManager;
    private NotificationManager mNotificationManager;
    private IconDrawableFactory mIconDrawableFactory;

    private PipMotionHelper mMotionHelper;

    // Used when building a deferred notification
    private String mDeferredNotificationPackageName;
    private int mDeferredNotificationUserId;

    private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
        @Override
        public void onOpChanged(String op, String packageName) {
            try {
                // Dismiss the PiP once the user disables the app ops setting for that package
                final Pair<ComponentName, Integer> topPipActivityInfo =
                        PipUtils.getTopPinnedActivity(mContext, mActivityManager);
                if (topPipActivityInfo.first != null) {
                    final ApplicationInfo appInfo = mContext.getPackageManager()
                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
                                mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
                                        packageName) != MODE_ALLOWED) {
                        mMotionHelper.dismissPip();
                    }
                }
            } catch (NameNotFoundException e) {
                // Unregister the listener if the package can't be found
                unregisterAppOpsListener();
            }
        }
    };

    public PipNotificationController(Context context, IActivityManager activityManager,
            PipMotionHelper motionHelper) {
        mContext = context;
        mActivityManager = activityManager;
        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        mNotificationManager = NotificationManager.from(context);
        mMotionHelper = motionHelper;
        mIconDrawableFactory = IconDrawableFactory.newInstance(context);
    }

    public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
        // Clear any existing notification
        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);

        if (deferUntilAnimationEnds) {
            mDeferredNotificationPackageName = packageName;
            mDeferredNotificationUserId = userId;
        } else {
            showNotificationForApp(packageName, userId);
        }

        // Register for changes to the app ops setting for this package while it is in PiP
        registerAppOpsListener(packageName);
    }

    public void onPinnedStackAnimationEnded() {
        if (mDeferredNotificationPackageName != null) {
            showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
            mDeferredNotificationPackageName = null;
            mDeferredNotificationUserId = 0;
        }
    }

    public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
        // Unregister for changes to the previously PiP'ed package
        unregisterAppOpsListener();

        // Reset the deferred notification package
        mDeferredNotificationPackageName = null;
        mDeferredNotificationUserId = 0;

        if (topPipActivity != null) {
            // onActivityUnpinned() is only called after the transition is complete, so we don't
            // need to defer until the animation ends to update the notification
            onActivityPinned(topPipActivity.getPackageName(), userId,
                    false /* deferUntilAnimationEnds */);
        } else {
            mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
        }
    }

    /**
     * Builds and shows the notification for the given app.
     */
    private void showNotificationForApp(String packageName, int userId) {
        // Build a new notification
        try {
            final UserHandle user = UserHandle.of(userId);
            final Context userContext = mContext.createPackageContextAsUser(
                    mContext.getPackageName(), 0, user);
            final Notification.Builder builder =
                    new Notification.Builder(userContext, NotificationChannels.GENERAL)
                            .setLocalOnly(true)
                            .setOngoing(true)
                            .setSmallIcon(R.drawable.pip_notification_icon)
                            .setColor(mContext.getColor(
                                    com.android.internal.R.color.system_notification_accent_color));
            if (updateNotificationForApp(builder, packageName, user)) {
                SystemUI.overrideNotificationAppName(mContext, builder);

                // Show the new notification
                mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
            }
        } catch (NameNotFoundException e) {
            Log.e(TAG, "Could not show notification for application", e);
        }
    }

    /**
     * Updates the notification builder with app-specific information, returning whether it was
     * successful.
     */
    private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
            UserHandle user) throws NameNotFoundException {
        final PackageManager pm = mContext.getPackageManager();
        final ApplicationInfo appInfo;
        try {
            appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
        } catch (NameNotFoundException e) {
            Log.e(TAG, "Could not update notification for application", e);
            return false;
        }

        if (appInfo != null) {
            final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
                    .toString();
            final String message = mContext.getString(R.string.pip_notification_message, appName);
            final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
                    Uri.fromParts("package", packageName, null));
            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
            settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);

            final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
            builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
                    .setContentText(message)
                    .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
                            settingsIntent, FLAG_CANCEL_CURRENT, null, user))
                    .setStyle(new Notification.BigTextStyle().bigText(message))
                    .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
            return true;
        }
        return false;
    }

    private void registerAppOpsListener(String packageName) {
        mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
                mAppOpsChangedListener);
    }

    private void unregisterAppOpsListener() {
        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
    }

    /**
     * Bakes a drawable into a bitmap.
     */
    private Bitmap createBitmap(Drawable d) {
        Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
                Config.ARGB_8888);
        Canvas c = new Canvas(bitmap);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        d.draw(c);
        c.setBitmap(null);
        return bitmap;
    }
}