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

Commit 8abf129f authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Removing PIP notification for menu action"

parents ff49fa53 a556fe6a
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
@@ -1895,6 +1895,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;
    }
}