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);
}
*/
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";
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 {
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) {
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;
}
/**
+ * 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 {
*/
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;
* @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>
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);
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)) {
}
}
- 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 {
}
}
- 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);
}
}
<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" />
<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>
<java-symbol type="string" name="config_defaultAssistantAccessPackage" />
<java-symbol type="bool" name="config_supportBluetoothPersistedState" />
+ <java-symbol type="string" name="slices_permission_request" />
</resources>
<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>
--- /dev/null
+<?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>
<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>
--- /dev/null
+/*
+ * 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();
+ }
+}
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
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;
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);
}
}
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;
}
}
+ 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();
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;
@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;
verifyCaller(pkg);
uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier());
enforceAccess(pkg, uri);
- getOrCreatePinnedSlice(uri).addSliceListener(listener, specs);
+ getOrCreatePinnedSlice(uri).addSliceListener(listener, pkg, specs);
}
@Override
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) {
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");
}
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) {
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.
lastPriority = ri.priority;
}
}
- return detected.getPackageName();
+ return detected != null ? detected.getPackageName() : null;
} finally {
Binder.restoreCallingIdentity(token);
}
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);
+ }
+ }
}
<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" />
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));
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));
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);
assertFalse(mPinnedSliceManager.isPinned());
- mPinnedSliceManager.addSliceListener(listener, FIRST_SPECS);
+ mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS);
mPinnedSliceManager.onChange();
TestableLooper.get(this).processAllMessages();