Revert "Revert "Slices permission model""

This reverts commit 1214c878a8.

Test: boot device
Bug: 72270082
Bug: 68751119
Change-Id: I7d0e709a04ffeb8b877aef539a978ee251a75742
This commit is contained in:
Jason Monk
2018-01-21 10:10:35 -05:00
parent 6ebcfe3109
commit e8f8be7698
15 changed files with 527 additions and 62 deletions

View 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);
}

View File

@@ -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.
*/

View File

@@ -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.
*/

View File

@@ -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);
}
}

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -2067,5 +2067,21 @@
<string name="touch_filtered_warning">Because an app is obscuring a permission request, Settings
cant 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>

View File

@@ -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();
}
}

View 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();

View File

@@ -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);
}
}
}

View 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" />

View 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();