Merge "Implement intent to uri mapping for slices"

This commit is contained in:
TreeHugger Robot
2017-11-15 02:52:53 +00:00
committed by Android (Google) Code Review
6 changed files with 148 additions and 17 deletions

View File

@@ -6974,6 +6974,7 @@ package android.app.slice {
public final class Slice implements android.os.Parcelable {
ctor protected Slice(android.os.Parcel);
method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent);
method public int describeContents();
method public java.util.List<java.lang.String> getHints();
method public java.util.List<android.app.slice.SliceItem> getItems();
@@ -7042,6 +7043,7 @@ package android.app.slice {
method public final java.lang.String getType(android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
method public android.net.Uri onMapIntentToUri(android.content.Intent);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);

View File

@@ -7418,6 +7418,7 @@ package android.app.slice {
public final class Slice implements android.os.Parcelable {
ctor protected Slice(android.os.Parcel);
method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent);
method public int describeContents();
method public java.util.List<java.lang.String> getHints();
method public java.util.List<android.app.slice.SliceItem> getItems();
@@ -7486,6 +7487,7 @@ package android.app.slice {
method public final java.lang.String getType(android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
method public android.net.Uri onMapIntentToUri(android.content.Intent);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);

View File

@@ -7048,6 +7048,7 @@ package android.app.slice {
public final class Slice implements android.os.Parcelable {
ctor protected Slice(android.os.Parcel);
method public static android.app.slice.Slice bindSlice(android.content.ContentResolver, android.net.Uri);
method public static android.app.slice.Slice bindSlice(android.content.Context, android.content.Intent);
method public int describeContents();
method public java.util.List<java.lang.String> getHints();
method public java.util.List<android.app.slice.SliceItem> getItems();
@@ -7116,6 +7117,7 @@ package android.app.slice {
method public final java.lang.String getType(android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public abstract android.app.slice.Slice onBindSlice(android.net.Uri);
method public android.net.Uri onMapIntentToUri(android.content.Intent);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);

View File

@@ -21,9 +21,12 @@ import android.annotation.Nullable;
import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.app.slice.widget.SliceView;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
@@ -424,4 +427,58 @@ public final class Slice implements Parcelable {
resolver.releaseProvider(provider);
}
}
/**
* Turns a slice intent into slice content. Expects an explicit intent. If there is no
* {@link ContentProvider} associated with the given intent this will throw
* {@link IllegalArgumentException}.
*
* @param context The context to use.
* @param intent The intent associated with a slice.
* @return The Slice provided by the app or null if none is given.
* @see Slice
* @see SliceProvider#onMapIntentToUri(Intent)
* @see Intent
*/
public static @Nullable Slice bindSlice(Context context, @NonNull Intent intent) {
Preconditions.checkNotNull(intent, "intent");
Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null,
"Slice intent must be explicit " + intent);
ContentResolver resolver = context.getContentResolver();
// Check if the intent has data for the slice uri on it and use that
final Uri intentData = intent.getData();
if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
return bindSlice(resolver, intentData);
}
// Otherwise ask the app
List<ResolveInfo> providers =
context.getPackageManager().queryIntentContentProviders(intent, 0);
if (providers == null) {
throw new IllegalArgumentException("Unable to resolve intent " + intent);
}
String authority = providers.get(0).providerInfo.authority;
Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority).build();
IContentProvider provider = resolver.acquireProvider(uri);
if (provider == null) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
final Bundle res = provider.call(resolver.getPackageName(),
SliceProvider.METHOD_MAP_INTENT, null, extras);
if (res == null) {
return null;
}
return res.getParcelable(SliceProvider.EXTRA_SLICE);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
resolver.releaseProvider(provider);
}
}
}

View File

@@ -16,10 +16,13 @@
package android.app.slice;
import android.Manifest.permission;
import android.annotation.NonNull;
import android.app.slice.widget.SliceView;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
@@ -37,29 +40,45 @@ import android.util.Log;
import java.util.concurrent.CountDownLatch;
/**
* A SliceProvider allows app to provide content to be displayed in system
* spaces. This content is templated and can contain actions, and the behavior
* of how it is surfaced is specific to the system surface.
* A SliceProvider allows an app to provide content to be displayed in system spaces. This content
* is templated and can contain actions, and the behavior of how it is surfaced is specific to the
* system surface.
* <p>
* Slices are not currently live content. They are bound once and shown to the user. If the content
* changes due to a callback from user interaction, then
* {@link ContentResolver#notifyChange(Uri, ContentObserver)} should be used to notify the system.
* </p>
* <p>
* The provider needs to be declared in the manifest to provide the authority for the app. The
* authority for most slices is expected to match the package of the application.
* </p>
*
* <p>Slices are not currently live content. They are bound once and shown to the
* user. If the content changes due to a callback from user interaction, then
* {@link ContentResolver#notifyChange(Uri, ContentObserver)}
* should be used to notify the system.</p>
*
* <p>The provider needs to be declared in the manifest to provide the authority
* for the app. The authority for most slices is expected to match the package
* of the application.</p>
* <pre class="prettyprint">
* {@literal
* <provider
* android:name="com.android.mypkg.MySliceProvider"
* android:authorities="com.android.mypkg" />}
* </pre>
* <p>
* Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider
* must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via
* an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an
* appropriate Uri representing the slice.
*
* <pre class="prettyprint">
* {@literal
* <provider
* android:name="com.android.mypkg.MySliceProvider"
* android:authorities="com.android.mypkg">
* <intent-filter>
* <action android:name="android.intent.action.MY_SLICE_INTENT" />
* </intent-filter>
* </provider>}
* </pre>
*
* @see Slice
*/
public abstract class SliceProvider extends ContentProvider {
/**
* This is the Android platform's MIME type for a slice: URI
* containing a slice implemented through {@link SliceProvider}.
@@ -75,6 +94,14 @@ public abstract class SliceProvider extends ContentProvider {
* @hide
*/
public static final String METHOD_SLICE = "bind_slice";
/**
* @hide
*/
public static final String METHOD_MAP_INTENT = "map_slice";
/**
* @hide
*/
public static final String EXTRA_INTENT = "slice_intent";
/**
* @hide
*/
@@ -98,6 +125,20 @@ public abstract class SliceProvider extends ContentProvider {
// TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
public abstract Slice onBindSlice(Uri sliceUri);
/**
* This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider.
* In that case, this method can be called and is expected to return a non-null Uri representing
* a slice. Otherwise this will throw {@link UnsupportedOperationException}.
*
* @return Uri representing the slice associated with the provided intent.
* @see {@link Slice}
* @see {@link SliceView#setSlice(Intent)}
*/
public @NonNull Uri onMapIntentToUri(Intent intent) {
throw new UnsupportedOperationException(
"This provider has not implemented intent to uri mapping");
}
@Override
public final int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
@@ -159,6 +200,19 @@ public abstract class SliceProvider extends ContentProvider {
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);
Uri uri = onMapIntentToUri(intent);
Bundle b = new Bundle();
if (uri != null) {
Slice s = handleBindSlice(uri);
b.putParcelable(EXTRA_SLICE, s);
} else {
b.putParcelable(EXTRA_SLICE, null);
}
return b;
}
return super.call(method, arg, extras);
}

View File

@@ -183,10 +183,25 @@ public class SliceView extends ViewGroup {
}
/**
* Populates this view with the {@link Slice} associated with the provided {@link Intent}. To
* use this method your app must have the permission
* {@link android.Manifest.permission#BIND_SLICE}).
* <p>
* Setting a slice differs from {@link #showSlice(Slice)} because it will ensure the view is
* updated with the slice identified by the provided intent changes. The lifecycle of this
* observer is handled by SliceView in {@link #onAttachedToWindow()} and
* {@link #onDetachedFromWindow()}. To unregister this observer outside of that you can call
* {@link #clearSlice}.
*
* @return true if a slice was found for the provided intent.
* @hide
*/
public void showSlice(Intent intent) {
// TODO
public boolean setSlice(@Nullable Intent intent) {
Slice s = Slice.bindSlice(mContext, intent);
if (s != null) {
return setSlice(s.getUri());
}
return s != null;
}
/**
@@ -199,8 +214,7 @@ public class SliceView extends ViewGroup {
* is handled by SliceView in {@link #onAttachedToWindow()} and {@link #onDetachedFromWindow()}.
* To unregister this observer outside of that you can call {@link #clearSlice}.
*
* @return true if the a slice was found for the provided uri.
* @see #clearSlice
* @return true if a slice was found for the provided uri.
*/
public boolean setSlice(@NonNull Uri sliceUri) {
Preconditions.checkNotNull(sliceUri,