Revert "Revert "Slices permission model""
This reverts commit 1214c878a8.
Test: boot device
Bug: 72270082
Bug: 68751119
Change-Id: I7d0e709a04ffeb8b877aef539a978ee251a75742
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -155,6 +155,13 @@ public final class Slice implements Parcelable {
|
||||
* content associated with this slice.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -53,11 +53,33 @@ 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
|
||||
*/
|
||||
@@ -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;
|
||||
@@ -357,6 +379,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.
|
||||
*/
|
||||
|
||||
@@ -15,13 +15,19 @@
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3192,10 +3192,14 @@
|
||||
<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" />
|
||||
|
||||
|
||||
@@ -4817,4 +4817,8 @@
|
||||
<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>
|
||||
|
||||
@@ -3227,4 +3227,5 @@
|
||||
<java-symbol type="string" name="config_defaultAssistantAccessPackage" />
|
||||
|
||||
<java-symbol type="bool" name="config_supportBluetoothPersistedState" />
|
||||
<java-symbol type="string" name="slices_permission_request" />
|
||||
</resources>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<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"/>
|
||||
@@ -435,6 +435,16 @@
|
||||
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"
|
||||
@@ -572,6 +582,7 @@
|
||||
|
||||
<provider android:name=".keyguard.KeyguardSliceProvider"
|
||||
android:authorities="com.android.systemui.keyguard"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="true">
|
||||
</provider>
|
||||
|
||||
|
||||
50
packages/SystemUI/res/layout/slice_permission_request.xml
Normal file
50
packages/SystemUI/res/layout/slice_permission_request.xml
Normal file
@@ -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>
|
||||
@@ -2067,5 +2067,21 @@
|
||||
<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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -16,27 +16,35 @@
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user