OSDN Git Service

Revert "Revert "Slices permission model""
authorJason Monk <jmonk@google.com>
Sun, 21 Jan 2018 15:10:35 +0000 (10:10 -0500)
committerJason Monk <jmonk@google.com>
Sun, 21 Jan 2018 16:53:28 +0000 (11:53 -0500)
This reverts commit 1214c878a839d99b1b29c8c31fc93af2ba949338.

Test: boot device
Bug: 72270082
Bug: 68751119
Change-Id: I7d0e709a04ffeb8b877aef539a978ee251a75742

15 files changed:
core/java/android/app/slice/ISliceManager.aidl
core/java/android/app/slice/Slice.java
core/java/android/app/slice/SliceManager.java
core/java/android/app/slice/SliceProvider.java
core/res/AndroidManifest.xml
core/res/res/values/strings.xml
core/res/res/values/symbols.xml
packages/SystemUI/AndroidManifest.xml
packages/SystemUI/res/layout/slice_permission_request.xml [new file with mode: 0644]
packages/SystemUI/res/values/strings.xml
packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java [new file with mode: 0644]
services/core/java/com/android/server/slice/PinnedSliceState.java
services/core/java/com/android/server/slice/SliceManagerService.java
services/tests/uiservicestests/AndroidManifest.xml
services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java

index 5f0e542..4461b16 100644 (file)
@@ -29,4 +29,6 @@ interface ISliceManager {
     void unpinSlice(String pkg, in Uri uri);
     boolean hasSliceAccess(String pkg);
     SliceSpec[] getPinnedSpecs(in Uri uri, String pkg);
+    int checkSlicePermission(in Uri uri, String pkg, int pid, int uid);
+    void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
 }
index 6093a4a..5bd3440 100644 (file)
@@ -156,6 +156,13 @@ public final class Slice implements Parcelable {
      */
     public static final String HINT_SEE_MORE = "see_more";
     /**
+     * A hint to tell the system that this slice cares about the return value of
+     * {@link SliceProvider#getBindingPackage} and should not cache the result
+     * for multiple apps.
+     * @hide
+     */
+    public static final String HINT_CALLER_NEEDED = "caller_needed";
+    /**
      * Key to retrieve an extra added to an intent when a control is changed.
      */
     public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE";
index 74864cb..09c420c 100644 (file)
@@ -53,12 +53,34 @@ public class SliceManager {
 
     private static final String TAG = "SliceManager";
 
+    /**
+     * @hide
+     */
+    public static final String ACTION_REQUEST_SLICE_PERMISSION =
+            "android.intent.action.REQUEST_SLICE_PERMISSION";
+
     private final ISliceManager mService;
     private final Context mContext;
     private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup =
             new ArrayMap<>();
 
     /**
+     * Permission denied.
+     * @hide
+     */
+    public static final int PERMISSION_DENIED = -1;
+    /**
+     * Permission granted.
+     * @hide
+     */
+    public static final int PERMISSION_GRANTED = 0;
+    /**
+     * Permission just granted by the user, and should be granted uri permission as well.
+     * @hide
+     */
+    public static final int PERMISSION_USER_GRANTED = 1;
+
+    /**
      * @hide
      */
     public SliceManager(Context context, Handler handler) throws ServiceNotFoundException {
@@ -284,7 +306,7 @@ public class SliceManager {
             extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
             extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
                     new ArrayList<>(supportedSpecs));
-            final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
+            final Bundle res = provider.call(mContext.getPackageName(), SliceProvider.METHOD_SLICE,
                     null, extras);
             Bundle.setDefusable(res, true);
             if (res == null) {
@@ -342,7 +364,7 @@ public class SliceManager {
             extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
             extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
                     new ArrayList<>(supportedSpecs));
-            final Bundle res = provider.call(resolver.getPackageName(),
+            final Bundle res = provider.call(mContext.getPackageName(),
                     SliceProvider.METHOD_MAP_INTENT, null, extras);
             if (res == null) {
                 return null;
@@ -358,6 +380,45 @@ public class SliceManager {
     }
 
     /**
+     * Does the permission check to see if a caller has access to a specific slice.
+     * @hide
+     */
+    public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid) {
+        try {
+            if (pkg == null) {
+                throw new SecurityException("No pkg specified");
+            }
+            int result = mService.checkSlicePermission(uri, pkg, pid, uid);
+            if (result == PERMISSION_DENIED) {
+                throw new SecurityException("User " + uid + " does not have slice permission for "
+                        + uri + ".");
+            }
+            if (result == PERMISSION_USER_GRANTED) {
+                // We just had a user grant of this permission and need to grant this to the app
+                // permanently.
+                mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(),
+                        Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Called by SystemUI to grant a slice permission after a dialog is shown.
+     * @hide
+     */
+    public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) {
+        try {
+            mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Class that listens to changes in {@link Slice}s.
      */
     public interface SliceCallback {
index aa41f14..8ffacf5 100644 (file)
  */
 package android.app.slice;
 
-import android.Manifest.permission;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
@@ -129,9 +135,41 @@ public abstract class SliceProvider extends ContentProvider {
      * @hide
      */
     public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants";
+    /**
+     * @hide
+     */
+    public static final String EXTRA_PKG = "pkg";
+    /**
+     * @hide
+     */
+    public static final String EXTRA_PROVIDER_PKG = "provider_pkg";
+    /**
+     * @hide
+     */
+    public static final String EXTRA_OVERRIDE_PKG = "override_pkg";
 
     private static final boolean DEBUG = false;
 
+    private String mBindingPkg;
+    private SliceManager mSliceManager;
+
+    /**
+     * Return the package name of the caller that initiated the binding request
+     * currently happening. The returned package will have been
+     * verified to belong to the calling UID. Returns {@code null} if not
+     * currently performing an {@link #onBindSlice(Uri, List)}.
+     * @hide
+     */
+    public final @Nullable String getBindingPackage() {
+        return mBindingPkg;
+    }
+
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        super.attachInfo(context, info);
+        mSliceManager = context.getSystemService(SliceManager.class);
+    }
+
     /**
      * Implemented to create a slice. Will be called on the main thread.
      * <p>
@@ -262,28 +300,27 @@ public abstract class SliceProvider extends ContentProvider {
     public Bundle call(String method, String arg, Bundle extras) {
         if (method.equals(METHOD_SLICE)) {
             Uri uri = extras.getParcelable(EXTRA_BIND_URI);
-            if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
-                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
-                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
-                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                        "Slice binding requires the permission BIND_SLICE");
-            }
             List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
 
-            Slice s = handleBindSlice(uri, supportedSpecs);
+            String callingPackage = getCallingPackage();
+            if (extras.containsKey(EXTRA_OVERRIDE_PKG)) {
+                if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                    throw new SecurityException("Only the system can override calling pkg");
+                }
+                callingPackage = extras.getString(EXTRA_OVERRIDE_PKG);
+            }
+            Slice s = handleBindSlice(uri, supportedSpecs, callingPackage);
             Bundle b = new Bundle();
             b.putParcelable(EXTRA_SLICE, s);
             return b;
         } else if (method.equals(METHOD_MAP_INTENT)) {
-            getContext().enforceCallingPermission(permission.BIND_SLICE,
-                    "Slice binding requires the permission BIND_SLICE");
             Intent intent = extras.getParcelable(EXTRA_INTENT);
             if (intent == null) return null;
             Uri uri = onMapIntentToUri(intent);
             List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
             Bundle b = new Bundle();
             if (uri != null) {
-                Slice s = handleBindSlice(uri, supportedSpecs);
+                Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage());
                 b.putParcelable(EXTRA_SLICE, s);
             } else {
                 b.putParcelable(EXTRA_SLICE, null);
@@ -291,20 +328,14 @@ public abstract class SliceProvider extends ContentProvider {
             return b;
         } else if (method.equals(METHOD_PIN)) {
             Uri uri = extras.getParcelable(EXTRA_BIND_URI);
-            if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
-                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
-                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
-                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                        "Slice binding requires the permission BIND_SLICE");
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("Only the system can pin/unpin slices");
             }
             handlePinSlice(uri);
         } else if (method.equals(METHOD_UNPIN)) {
             Uri uri = extras.getParcelable(EXTRA_BIND_URI);
-            if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
-                getContext().enforceUriPermission(uri, permission.BIND_SLICE,
-                        permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
-                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                        "Slice binding requires the permission BIND_SLICE");
+            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                throw new SecurityException("Only the system can pin/unpin slices");
             }
             handleUnpinSlice(uri);
         } else if (method.equals(METHOD_GET_DESCENDANTS)) {
@@ -370,14 +401,27 @@ public abstract class SliceProvider extends ContentProvider {
         }
     }
 
-    private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
+    private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs,
+            String callingPkg) {
+        // This can be removed once Slice#bindSlice is removed and everyone is using
+        // SliceManager#bindSlice.
+        String pkg = callingPkg != null ? callingPkg
+                : getContext().getPackageManager().getNameForUid(Binder.getCallingUid());
+        if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) {
+            try {
+                mSliceManager.enforceSlicePermission(sliceUri, pkg,
+                        Binder.getCallingPid(), Binder.getCallingUid());
+            } catch (SecurityException e) {
+                return createPermissionSlice(getContext(), sliceUri, pkg);
+            }
+        }
         if (Looper.myLooper() == Looper.getMainLooper()) {
-            return onBindSliceStrict(sliceUri, supportedSpecs);
+            return onBindSliceStrict(sliceUri, supportedSpecs, pkg);
         } else {
             CountDownLatch latch = new CountDownLatch(1);
             Slice[] output = new Slice[1];
             Handler.getMain().post(() -> {
-                output[0] = onBindSliceStrict(sliceUri, supportedSpecs);
+                output[0] = onBindSliceStrict(sliceUri, supportedSpecs, pkg);
                 latch.countDown();
             });
             try {
@@ -389,15 +433,66 @@ public abstract class SliceProvider extends ContentProvider {
         }
     }
 
-    private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) {
+    /**
+     * @hide
+     */
+    public static Slice createPermissionSlice(Context context, Uri sliceUri,
+            String callingPackage) {
+        return new Slice.Builder(sliceUri)
+                .addAction(createPermissionIntent(context, sliceUri, callingPackage),
+                        new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build())
+                                .addText(getPermissionString(context, callingPackage), null)
+                                .build())
+                .addHints(Slice.HINT_LIST_ITEM)
+                .build();
+    }
+
+    /**
+     * @hide
+     */
+    public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
+            String callingPackage) {
+        Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
+        intent.setComponent(new ComponentName("com.android.systemui",
+                "com.android.systemui.SlicePermissionActivity"));
+        intent.putExtra(EXTRA_BIND_URI, sliceUri);
+        intent.putExtra(EXTRA_PKG, callingPackage);
+        intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
+        // Unique pending intent.
+        intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
+                .build());
+
+        return PendingIntent.getActivity(context, 0, intent, 0);
+    }
+
+    /**
+     * @hide
+     */
+    public static CharSequence getPermissionString(Context context, String callingPackage) {
+        PackageManager pm = context.getPackageManager();
+        try {
+            return context.getString(
+                    com.android.internal.R.string.slices_permission_request,
+                    pm.getApplicationInfo(callingPackage, 0).loadLabel(pm),
+                    context.getApplicationInfo().loadLabel(pm));
+        } catch (NameNotFoundException e) {
+            // This shouldn't be possible since the caller is verified.
+            throw new RuntimeException("Unknown calling app", e);
+        }
+    }
+
+    private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs,
+            String callingPackage) {
         ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
         try {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                     .detectAll()
                     .penaltyDeath()
                     .build());
+            mBindingPkg = callingPackage;
             return onBindSlice(sliceUri, supportedSpecs);
         } finally {
+            mBindingPkg = null;
             StrictMode.setThreadPolicy(oldPolicy);
         }
     }
index e4e46f6..990c574 100644 (file)
     <permission android:name="android.permission.BIND_APPWIDGET"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide Allows sysui to manage user grants of slice permissions. -->
+    <permission android:name="android.permission.MANAGE_SLICE_PERMISSIONS"
+        android:protectionLevel="signature" />
+
     <!-- Allows an application to bind app's slices and get their
          content. This content will be surfaced to the
          user and not to leave the device.
-         <p>Not for use by third-party applications. -->
+         <p>Not for use by third-party applications.-->
     <permission android:name="android.permission.BIND_SLICE"
         android:protectionLevel="signature|privileged|development" />
 
index 2cfe919..0c844c9 100644 (file)
     <string name="harmful_app_warning_launch_anyway">Launch anyway</string>
     <!-- Title for the harmful app warning dialog. -->
     <string name="harmful_app_warning_title">Uninstall harmful app?</string>
+
+    <!-- Text describing a permission request for one app to show another app's
+         slices [CHAR LIMIT=NONE] -->
+    <string name="slices_permission_request"><xliff:g id="app" example="Example App">%1$s</xliff:g> wants to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices</string>
 </resources>
index 5309115..03a800d 100644 (file)
   <java-symbol type="string" name="config_defaultAssistantAccessPackage" />
 
   <java-symbol type="bool" name="config_supportBluetoothPersistedState" />
+  <java-symbol type="string" name="slices_permission_request" />
 </resources>
index aa2cdbb..80ac825 100644 (file)
     <uses-permission android:name="android.permission.TRUST_LISTENER" />
     <uses-permission android:name="android.permission.USE_FINGERPRINT" />
     <uses-permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT" />
-    <uses-permission android:name="android.permission.BIND_SLICE" />
+    <uses-permission android:name="android.permission.MANAGE_SLICE_PERMISSIONS" />
 
     <!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
     <uses-permission android:name="android.permission.SET_WALLPAPER"/>
             android:launchMode="singleTop"
             androidprv:alwaysFocusable="true" />
 
+        <!-- started from SliceProvider -->
+        <activity android:name=".SlicePermissionActivity"
+            android:theme="@style/Theme.SystemUI.Dialog.Alert"
+            android:finishOnCloseSystemDialogs="true"
+            android:excludeFromRecents="true">
+            <intent-filter>
+                <action android:name="android.intent.action.REQUEST_SLICE_PERMISSION" />
+            </intent-filter>
+        </activity>
+
         <!-- platform logo easter egg activity -->
         <activity
             android:name=".DessertCase"
 
         <provider android:name=".keyguard.KeyguardSliceProvider"
                   android:authorities="com.android.systemui.keyguard"
+                  android:grantUriPermissions="true"
                   android:exported="true">
         </provider>
 
diff --git a/packages/SystemUI/res/layout/slice_permission_request.xml b/packages/SystemUI/res/layout/slice_permission_request.xml
new file mode 100644 (file)
index 0000000..cdb2a91
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 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.
+-->
+<!-- Extends LinearLayout -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="16dp"
+    android:paddingEnd="16dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/text2"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="8dp"
+        android:paddingStart="8dp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="@string/slice_permission_text_1" />
+
+    <TextView
+        android:id="@+id/text1"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="8dp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:paddingBottom="16dp"
+        android:text="@string/slice_permission_text_2" />
+
+    <CheckBox
+        android:id="@+id/slice_permission_checkbox"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/slice_permission_checkbox" />
+
+</LinearLayout>
index 99ba369..199ccfc 100644 (file)
     <string name="touch_filtered_warning">Because an app is obscuring a permission request, Settings
         can’t verify your response.</string>
 
+    <!-- Title of prompt requesting access to display slices [CHAR LIMIT=NONE] -->
+    <string name="slice_permission_title">Allow <xliff:g id="app" example="Example App">%1$s</xliff:g> to show <xliff:g id="app_2" example="Other Example App">%2$s</xliff:g> slices?</string>
+
+    <!-- Description of what kind of access is given to a slice host [CHAR LIMIT=NONE] -->
+    <string name="slice_permission_text_1"> - It can read information from <xliff:g id="app" example="Example App">%1$s</xliff:g></string>
+    <!-- Description of what kind of access is given to a slice host [CHAR LIMIT=NONE] -->
+    <string name="slice_permission_text_2"> - It can take actions inside <xliff:g id="app" example="Example App">%1$s</xliff:g></string>
+
+    <!-- Text on checkbox allowing the app to show slices from all apps [CHAR LIMIT=NONE] -->
+    <string name="slice_permission_checkbox">Allow <xliff:g id="app" example="Example App">%1$s</xliff:g> to show slices from any app</string>
+
+    <!-- Option to grant the slice permission request on the screen [CHAR LIMIT=15] -->
+    <string name="slice_permission_allow">Allow</string>
+
+    <!-- Option to grant the slice permission request on the screen [CHAR LIMIT=15] -->
+    <string name="slice_permission_deny">Deny</string>
 
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java
new file mode 100644 (file)
index 0000000..302face
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.slice.SliceManager;
+import android.app.slice.SliceProvider;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+public class SlicePermissionActivity extends Activity implements OnClickListener,
+        OnDismissListener {
+
+    private static final String TAG = "SlicePermissionActivity";
+
+    private CheckBox mAllCheckbox;
+
+    private Uri mUri;
+    private String mCallingPkg;
+    private String mProviderPkg;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI);
+        mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG);
+        mProviderPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PROVIDER_PKG);
+
+        try {
+            PackageManager pm = getPackageManager();
+            CharSequence app1 = pm.getApplicationInfo(mCallingPkg, 0).loadLabel(pm);
+            CharSequence app2 = pm.getApplicationInfo(mProviderPkg, 0).loadLabel(pm);
+            AlertDialog dialog = new AlertDialog.Builder(this)
+                    .setTitle(getString(R.string.slice_permission_title, app1, app2))
+                    .setView(R.layout.slice_permission_request)
+                    .setNegativeButton(R.string.slice_permission_deny, this)
+                    .setPositiveButton(R.string.slice_permission_allow, this)
+                    .setOnDismissListener(this)
+                    .show();
+            TextView t1 = dialog.getWindow().getDecorView().findViewById(R.id.text1);
+            t1.setText(getString(R.string.slice_permission_text_1, app2));
+            TextView t2 = dialog.getWindow().getDecorView().findViewById(R.id.text2);
+            t2.setText(getString(R.string.slice_permission_text_2, app2));
+            mAllCheckbox = dialog.getWindow().getDecorView().findViewById(
+                    R.id.slice_permission_checkbox);
+            mAllCheckbox.setText(getString(R.string.slice_permission_checkbox, app1));
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Couldn't find package", e);
+            finish();
+        }
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            getSystemService(SliceManager.class).grantPermissionFromUser(mUri, mCallingPkg,
+                    mAllCheckbox.isChecked());
+        }
+        finish();
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        finish();
+    }
+}
index cf930f5..09f6da9 100644 (file)
@@ -22,6 +22,7 @@ import android.content.ContentProviderClient;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -51,6 +52,8 @@ public class PinnedSliceState {
     private final ArraySet<ISliceListener> mListeners = new ArraySet<>();
     @GuardedBy("mLock")
     private SliceSpec[] mSupportedSpecs = null;
+    @GuardedBy("mLock")
+    private final ArrayMap<ISliceListener, String> mPkgMap = new ArrayMap<>();
 
     public PinnedSliceState(SliceManagerService service, Uri uri) {
         mService = service;
@@ -102,17 +105,19 @@ public class PinnedSliceState {
         mService.getHandler().post(this::handleBind);
     }
 
-    public void addSliceListener(ISliceListener listener, SliceSpec[] specs) {
+    public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs) {
         synchronized (mLock) {
             if (mListeners.add(listener) && mListeners.size() == 1) {
                 mService.listen(mUri);
             }
+            mPkgMap.put(listener, pkg);
             mergeSpecs(specs);
         }
     }
 
     public boolean removeSliceListener(ISliceListener listener) {
         synchronized (mLock) {
+            mPkgMap.remove(listener);
             if (mListeners.remove(listener) && mListeners.size() == 0) {
                 mService.unlisten(mUri);
             }
@@ -155,25 +160,16 @@ public class PinnedSliceState {
     }
 
     private void handleBind() {
-        Slice s;
-        try (ContentProviderClient client = getClient()) {
-            Bundle extras = new Bundle();
-            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri);
-            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
-                    new ArrayList<>(Arrays.asList(mSupportedSpecs)));
-            final Bundle res;
-            try {
-                res = client.call(SliceProvider.METHOD_SLICE, null, extras);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Unable to bind slice " + mUri, e);
-                return;
-            }
-            if (res == null) return;
-            Bundle.setDefusable(res, true);
-            s = res.getParcelable(SliceProvider.EXTRA_SLICE);
-        }
+        Slice cachedSlice = doBind(null);
         synchronized (mLock) {
             mListeners.removeIf(l -> {
+                Slice s = cachedSlice;
+                if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED)) {
+                    s = doBind(mPkgMap.get(l));
+                }
+                if (s == null) {
+                    return true;
+                }
                 try {
                     l.onSliceUpdated(s);
                     return false;
@@ -189,6 +185,26 @@ public class PinnedSliceState {
         }
     }
 
+    private Slice doBind(String overridePkg) {
+        try (ContentProviderClient client = getClient()) {
+            Bundle extras = new Bundle();
+            extras.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri);
+            extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+                    new ArrayList<>(Arrays.asList(mSupportedSpecs)));
+            extras.putString(SliceProvider.EXTRA_OVERRIDE_PKG, overridePkg);
+            final Bundle res;
+            try {
+                res = client.call(SliceProvider.METHOD_SLICE, null, extras);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to bind slice " + mUri, e);
+                return null;
+            }
+            if (res == null) return null;
+            Bundle.setDefusable(res, true);
+            return res.getParcelable(SliceProvider.EXTRA_SLICE);
+        }
+    }
+
     private void handleSendPinned() {
         try (ContentProviderClient client = getClient()) {
             Bundle b = new Bundle();
index 2d9e772..ca7632c 100644 (file)
 
 package com.android.server.slice;
 
+import static android.content.ContentProvider.getUriWithoutUserId;
 import static android.content.ContentProvider.getUserIdFromUri;
 import static android.content.ContentProvider.maybeAddUserId;
 
 import android.Manifest.permission;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.ContentProviderHolder;
+import android.app.IActivityManager;
 import android.app.slice.ISliceListener;
 import android.app.slice.ISliceManager;
+import android.app.slice.SliceManager;
 import android.app.slice.SliceSpec;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -63,6 +71,8 @@ public class SliceManagerService extends ISliceManager.Stub {
 
     @GuardedBy("mLock")
     private final ArrayMap<Uri, PinnedSliceState> mPinnedSlicesByUri = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>();
     private final Handler mHandler;
     private final ContentObserver mObserver;
 
@@ -111,7 +121,7 @@ public class SliceManagerService extends ISliceManager.Stub {
         verifyCaller(pkg);
         uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
         enforceAccess(pkg, uri);
-        getOrCreatePinnedSlice(uri).addSliceListener(listener, specs);
+        getOrCreatePinnedSlice(uri).addSliceListener(listener, pkg, specs);
     }
 
     @Override
@@ -156,6 +166,43 @@ public class SliceManagerService extends ISliceManager.Stub {
         return getPinnedSlice(uri).getSpecs();
     }
 
+    @Override
+    public int checkSlicePermission(Uri uri, String pkg, int pid, int uid) throws RemoteException {
+        if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+                == PackageManager.PERMISSION_GRANTED) {
+            return SliceManager.PERMISSION_GRANTED;
+        }
+        if (hasFullSliceAccess(pkg, uid)) {
+            return SliceManager.PERMISSION_GRANTED;
+        }
+        synchronized (mLock) {
+            if (mUserGrants.contains(new SliceGrant(uri, pkg))) {
+                return SliceManager.PERMISSION_USER_GRANTED;
+            }
+        }
+        return SliceManager.PERMISSION_DENIED;
+    }
+
+    @Override
+    public void grantPermissionFromUser(Uri uri, String pkg, String callingPkg, boolean allSlices) {
+        verifyCaller(callingPkg);
+        getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS,
+                "Slice granting requires MANAGE_SLICE_PERMISSIONS");
+        if (allSlices) {
+            // TODO: Manage full access grants.
+        } else {
+            synchronized (mLock) {
+                mUserGrants.add(new SliceGrant(uri, pkg));
+            }
+            long ident = Binder.clearCallingIdentity();
+            try {
+                mContext.getContentResolver().notifyChange(uri, null);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
     ///  ----- internal code -----
     void removePinnedSlice(Uri uri) {
         synchronized (mLock) {
@@ -202,12 +249,45 @@ public class SliceManagerService extends ISliceManager.Stub {
         return mHandler;
     }
 
-    private void enforceAccess(String pkg, Uri uri) {
-        getContext().enforceUriPermission(uri, permission.BIND_SLICE,
-                permission.BIND_SLICE, Binder.getCallingPid(), Binder.getCallingUid(),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                "Slice binding requires the permission BIND_SLICE");
+    private void enforceAccess(String pkg, Uri uri) throws RemoteException {
         int user = Binder.getCallingUserHandle().getIdentifier();
+        // Check for default launcher/assistant.
+        if (!hasFullSliceAccess(pkg, Binder.getCallingUid())) {
+            try {
+                // Also allow things with uri access.
+                getContext().enforceUriPermission(uri, Binder.getCallingPid(),
+                        Binder.getCallingUid(),
+                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                        "Slice binding requires permission to the Uri");
+            } catch (SecurityException e) {
+                // Last fallback (if the calling app owns the authority, then it can have access).
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    IBinder token = new Binder();
+                    IActivityManager activityManager = ActivityManager.getService();
+                    ContentProviderHolder holder = null;
+                    String providerName = getUriWithoutUserId(uri).getAuthority();
+                    try {
+                        holder = activityManager.getContentProviderExternal(
+                                providerName, getUserIdFromUri(uri, user), token);
+                        if (holder == null || holder.info == null
+                                || !Objects.equals(holder.info.packageName, pkg)) {
+                            // No more fallbacks, no access.
+                            throw e;
+                        }
+                    } finally {
+                        if (holder != null && holder.provider != null) {
+                            activityManager.removeContentProviderExternal(providerName, token);
+                        }
+                    }
+                } finally {
+                    // I know, the double finally seems ugly, but seems safest for the identity.
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+        // Lastly check for any multi-userness. Any return statements above here will break this
+        // important check.
         if (getUserIdFromUri(uri, user) != user) {
             getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL,
                     "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL");
@@ -230,8 +310,14 @@ public class SliceManagerService extends ISliceManager.Stub {
     }
 
     private boolean hasFullSliceAccess(String pkg, int userId) {
-        return isDefaultHomeApp(pkg, userId) || isAssistant(pkg, userId)
-                || isGrantedFullAccess(pkg, userId);
+        long ident = Binder.clearCallingIdentity();
+        try {
+            boolean ret = isDefaultHomeApp(pkg, userId) || isAssistant(pkg, userId)
+                    || isGrantedFullAccess(pkg, userId);
+            return ret;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 
     private boolean isAssistant(String pkg, int userId) {
@@ -259,7 +345,8 @@ public class SliceManagerService extends ISliceManager.Stub {
 
     private boolean isDefaultHomeApp(String pkg, int userId) {
         String defaultHome = getDefaultHome(userId);
-        return Objects.equals(pkg, defaultHome);
+
+        return pkg != null && Objects.equals(pkg, defaultHome);
     }
 
     // Based on getDefaultHome in ShortcutService.
@@ -301,7 +388,7 @@ public class SliceManagerService extends ISliceManager.Stub {
                     lastPriority = ri.priority;
                 }
             }
-            return detected.getPackageName();
+            return detected != null ? detected.getPackageName() : null;
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -349,4 +436,26 @@ public class SliceManagerService extends ISliceManager.Stub {
             mService.onStopUser(userHandle);
         }
     }
+
+    private class SliceGrant {
+        private final Uri mUri;
+        private final String mPkg;
+
+        public SliceGrant(Uri uri, String pkg) {
+            mUri = uri;
+            mPkg = pkg;
+        }
+
+        @Override
+        public int hashCode() {
+            return mUri.hashCode() + mPkg.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof SliceGrant)) return false;
+            SliceGrant other = (SliceGrant) obj;
+            return Objects.equals(other.mUri, mUri) && Objects.equals(other.mPkg, mPkg);
+        }
+    }
 }
index f022dcf..3475572 100644 (file)
@@ -25,6 +25,7 @@
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+    <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
index ce328c2..aada682 100644 (file)
@@ -149,7 +149,7 @@ public class PinnedSliceStateTest extends UiServiceTestCase {
         ISliceListener listener = mock(ISliceListener.class);
         assertFalse(mPinnedSliceManager.isPinned());
 
-        mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
+        mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
         assertTrue(mPinnedSliceManager.isPinned());
 
         assertTrue(mPinnedSliceManager.removeSliceListener(listener));
@@ -162,9 +162,9 @@ public class PinnedSliceStateTest extends UiServiceTestCase {
         ISliceListener listener2 = mock(ISliceListener.class);
         assertFalse(mPinnedSliceManager.isPinned());
 
-        mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
+        mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
         assertTrue(mPinnedSliceManager.isPinned());
-        mPinnedSliceManager.addSliceListener(listener2, FIRST_SPECS);
+        mPinnedSliceManager.addSliceListener(listener2, mContext.getPackageName(), FIRST_SPECS);
 
         assertFalse(mPinnedSliceManager.removeSliceListener(listener));
         assertTrue(mPinnedSliceManager.removeSliceListener(listener2));
@@ -176,7 +176,7 @@ public class PinnedSliceStateTest extends UiServiceTestCase {
         ISliceListener listener = mock(ISliceListener.class);
         assertFalse(mPinnedSliceManager.isPinned());
 
-        mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
+        mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
         assertTrue(mPinnedSliceManager.isPinned());
         mPinnedSliceManager.pin("pkg", FIRST_SPECS);
 
@@ -199,7 +199,7 @@ public class PinnedSliceStateTest extends UiServiceTestCase {
 
         assertFalse(mPinnedSliceManager.isPinned());
 
-        mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
+        mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
 
         mPinnedSliceManager.onChange();
         TestableLooper.get(this).processAllMessages();