OSDN Git Service

Adding AppOps setting for entering PiP when hidden.
authorWinson Chung <winsonc@google.com>
Sat, 21 Jan 2017 00:14:51 +0000 (16:14 -0800)
committerWinson Chung <winsonc@google.com>
Tue, 24 Jan 2017 17:15:01 +0000 (09:15 -0800)
- In O, apps can request to enter picture-in-picture when the user
  effectively leaves their activity for another task by default. To
  prevent this from being abused, we need to add a setting for the
  user to disable this behavior per-package in the system level.
  When disabled, any activity from that package will only be able
  to enter picture-in-picture when it is visible and resumed.

Bug: 34520451
Test: android.server.cts.ActivityManagerPinnedStackTests
Test: #testAppOpsDenyPipOnPause
Change-Id: Ib3a993e99ffb071706c6b7d3fb1c882b74acc5d7
Signed-off-by: Winson Chung <winsonc@google.com>
core/java/android/app/AppOpsManager.java
core/java/android/provider/Settings.java
proto/src/metrics_constants.proto
services/core/java/com/android/server/am/ActivityRecord.java

index 9cc13ab..603126b 100644 (file)
@@ -245,8 +245,10 @@ public class AppOpsManager {
     public static final int OP_READ_PHONE_NUMBER = 65;
     /** @hide Request package installs through package installer */
     public static final int OP_REQUEST_INSTALL_PACKAGES = 66;
+    /** @hide Enter picture-in-picture when hidden. */
+    public static final int OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE = 67;
     /** @hide */
-    public static final int _NUM_OP = 67;
+    public static final int _NUM_OP = 68;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -464,6 +466,7 @@ public class AppOpsManager {
             OP_AUDIO_ACCESSIBILITY_VOLUME,
             OP_READ_PHONE_NUMBER,
             OP_REQUEST_INSTALL_PACKAGES,
+            OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
     };
 
     /**
@@ -538,6 +541,7 @@ public class AppOpsManager {
             null, // OP_AUDIO_ACCESSIBILITY_VOLUME
             OPSTR_READ_PHONE_NUMBER,
             null, // OP_REQUEST_INSTALL_PACKAGES
+            null,
     };
 
     /**
@@ -612,6 +616,7 @@ public class AppOpsManager {
             "AUDIO_ACCESSIBILITY_VOLUME",
             "READ_PHONE_NUMBER",
             "REQUEST_INSTALL_PACKAGES",
+            "OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE",
     };
 
     /**
@@ -686,6 +691,7 @@ public class AppOpsManager {
             null, // no permission for changing accessibility volume
             Manifest.permission.READ_PHONE_NUMBER,
             Manifest.permission.REQUEST_INSTALL_PACKAGES,
+            null, // no permission for entering picture-in-picture on hide
     };
 
     /**
@@ -761,6 +767,7 @@ public class AppOpsManager {
             UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
             null, // READ_PHONE_NUMBER
             null, // REQUEST_INSTALL_PACKAGES
+            null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
     };
 
     /**
@@ -835,6 +842,7 @@ public class AppOpsManager {
             false, // AUDIO_ACCESSIBILITY_VOLUME
             false, // READ_PHONE_NUMBER
             false, // REQUEST_INSTALL_PACKAGES
+            false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
     };
 
     /**
@@ -908,6 +916,7 @@ public class AppOpsManager {
             AppOpsManager.MODE_ALLOWED,  // OP_AUDIO_ACCESSIBILITY_VOLUME
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_DEFAULT, // OP_REQUEST_INSTALL_PACKAGES
+            AppOpsManager.MODE_ALLOWED,  // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
     };
 
     /**
@@ -985,6 +994,7 @@ public class AppOpsManager {
             false, // OP_AUDIO_ACCESSIBILITY_VOLUME
             false,
             false, // OP_REQUEST_INSTALL_PACKAGES
+            false, // OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE
     };
 
     /**
index 71b9482..4ab815d 100755 (executable)
@@ -1334,6 +1334,19 @@ public final class Settings {
             = "android.settings.VR_LISTENER_SETTINGS";
 
     /**
+     * Activity Action: Show Picture-in-picture settings.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PICTURE_IN_PICTURE_SETTINGS
+            = "android.settings.PICTURE_IN_PICTURE_SETTINGS";
+
+    /**
      * Activity Action: Show Storage Manager settings.
      * <p>
      * Input: Nothing.
index 96c468c..341438d 100644 (file)
@@ -3321,6 +3321,16 @@ message MetricsEvent {
     // Indicates number of terms read on the terms screen.
     PROVISIONING_TERMS_READ = 811;
 
+    // Logs that the user has edited the picture-in-picture settings.
+    // CATEGORY: SETTINGS
+    SETTINGS_MANAGE_PICTURE_IN_PICTURE = 812;
+
+    // ACTION: Allow "Enable picture-in-picture on hide" for an app
+    APP_PICTURE_IN_PICTURE_ON_HIDE_ALLOW = 813;
+
+    // ACTION: Deny "Enable picture-in-picture on hide" for an app
+    APP_PICTURE_IN_PICTURE_ON_HIDE_DENY = 814;
+
     // ---- End O Constants, all O constants go above this line ----
 
     // Add new aosp constants above this line.
index a968e0b..3bd44f5 100644 (file)
@@ -22,6 +22,8 @@ import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
@@ -64,6 +66,7 @@ import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 import android.annotation.NonNull;
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityOptions;
+import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.PictureInPictureArgs;
 import android.app.ResultInfo;
@@ -75,6 +78,7 @@ import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.IBinder;
@@ -911,13 +915,15 @@ final class ActivityRecord implements AppWindowContainerListener {
             case PAUSED:
                 // When pausing, only allow enter PiP if not on the lockscreen and there is not
                 // already an existing PiP activity
-                return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing;
+                return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing
+                        && checkEnterPictureInPictureOnHideAppOpsState();
             case STOPPING:
                 // When stopping in a valid state, then only allow enter PiP as in the pause state.
                 // Otherwise, fall through to throw an exception if the caller is trying to enter
                 // PiP in an invalid stopping state.
                 if (supportsPictureInPictureWhilePausing) {
-                    return !isKeyguardLocked && !hasPinnedStack;
+                    return !isKeyguardLocked && !hasPinnedStack
+                            && checkEnterPictureInPictureOnHideAppOpsState();
                 }
             default:
                 throw new IllegalStateException(caller
@@ -926,6 +932,19 @@ final class ActivityRecord implements AppWindowContainerListener {
         }
     }
 
+    /**
+     * @return Whether AppOps allows this package to enter picture-in-picture when it is hidden.
+     */
+    private boolean checkEnterPictureInPictureOnHideAppOpsState() {
+        try {
+            return service.getAppOpsService().checkOperation(OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE,
+                    appInfo.uid, packageName) == MODE_ALLOWED;
+        } catch (RemoteException e) {
+            // Local call
+        }
+        return false;
+    }
+
     boolean canGoInDockedStack() {
         return !isHomeActivity() && isResizeableOrForced();
     }