Add LiveData support for Slices
Switch SliceView to be a static rendering that just has a setSlice.
Also make SliceView a LiveData.Observer, and provide LiveData
implementation for listening to Slice changes.
Test: slices sample app
Bug: 68378561
Change-Id: Ib451c6e26a3af0f5335596fb70658f55eee639f3
diff --git a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
index 8986630..a4f28c1 100644
--- a/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
+++ b/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser.java
@@ -16,7 +16,8 @@
package com.example.androidx.slice.demos;
-import android.app.Activity;
+import android.app.slice.Slice;
+import android.arch.lifecycle.LiveData;
import android.content.ContentResolver;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
@@ -27,6 +28,8 @@
import android.os.Bundle;
import android.provider.BaseColumns;
import android.support.annotation.RequiresApi;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Menu;
@@ -36,12 +39,12 @@
import android.widget.CursorAdapter;
import android.widget.SearchView;
import android.widget.SimpleCursorAdapter;
-import android.widget.Toolbar;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
+import androidx.app.slice.widget.SliceLiveData;
import androidx.app.slice.widget.SliceView;
/**
@@ -49,7 +52,7 @@
* then displayed in the selected mode with SliceView.
*/
@RequiresApi(api = 28)
-public class SliceBrowser extends Activity {
+public class SliceBrowser extends AppCompatActivity {
private static final String TAG = "SlicePresenter";
@@ -61,6 +64,7 @@
private SearchView mSearchView;
private SimpleCursorAdapter mAdapter;
private SubMenu mTypeMenu;
+ private LiveData<Slice> mSliceLiveData;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -68,7 +72,7 @@
setContentView(R.layout.activity_layout);
Toolbar toolbar = findViewById(R.id.search_toolbar);
- setActionBar(toolbar);
+ setSupportActionBar(toolbar);
// Shows the slice
mContainer = findViewById(R.id.slice_preview);
@@ -155,6 +159,7 @@
@Override
protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
outState.putInt("SELECTED_MODE", mSelectedMode);
outState.putString("SELECTED_QUERY", mSearchView.getQuery().toString());
}
@@ -184,10 +189,14 @@
if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
SliceView v = new SliceView(getApplicationContext());
v.setTag(uri);
+ if (mSliceLiveData != null) {
+ mSliceLiveData.removeObservers(this);
+ }
mContainer.removeAllViews();
mContainer.addView(v);
+ mSliceLiveData = SliceLiveData.fromUri(this, uri);
v.setMode(mSelectedMode);
- v.setSlice(uri);
+ mSliceLiveData.observe(this, v);
} else {
Log.w(TAG, "Invalid uri, skipping slice: " + uri);
}
diff --git a/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml b/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml
index 833414c..6a087a3 100644
--- a/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml
+++ b/samples/SupportSliceDemos/src/main/res/layout/activity_layout.xml
@@ -35,7 +35,7 @@
app:cardCornerRadius="2dp"
app:cardBackgroundColor="?android:attr/colorBackground"
app:cardElevation="2dp">
- <Toolbar
+ <android.support.v7.widget.Toolbar
android:id="@+id/search_toolbar"
android:layout_width="match_parent"
android:layout_height="48dp"
@@ -51,7 +51,7 @@
android:imeOptions="actionSearch|flagNoExtractUi"
android:queryHint="content://..."
android:searchIcon="@null"/>
- </Toolbar>
+ </android.support.v7.widget.Toolbar>
</android.support.v7.widget.CardView>
</FrameLayout>
diff --git a/slices/view/build.gradle b/slices/view/build.gradle
index 72f0064..9ce7167 100644
--- a/slices/view/build.gradle
+++ b/slices/view/build.gradle
@@ -21,6 +21,7 @@
dependencies {
implementation project(":slices-core")
implementation libs.support.recyclerview, libs.support_exclude_config
+ implementation project(':lifecycle:extensions')
}
android {
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceLiveData.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceLiveData.java
new file mode 100644
index 0000000..eaaef50
--- /dev/null
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceLiveData.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 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 androidx.app.slice.widget;
+
+import android.app.slice.Slice;
+import android.arch.lifecycle.LiveData;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+
+/**
+ * Class with factory methods for creating LiveData that observes slices.
+ *
+ * @see #fromUri(Context, Uri)
+ * @see LiveData
+ */
+public final class SliceLiveData {
+
+ /**
+ * Produces an {@link LiveData} that tracks a Slice for a given Uri. To use
+ * this method your app must have the permission to the slice Uri or hold
+ * {@link android.Manifest.permission#BIND_SLICE}).
+ */
+ public static LiveData<Slice> fromUri(Context context, Uri uri) {
+ return new SliceLiveDataImpl(context.getApplicationContext(), uri);
+ }
+
+ private static class SliceLiveDataImpl extends LiveData<Slice> {
+ private final Uri mUri;
+ private final Context mContext;
+
+ private SliceLiveDataImpl(Context context, Uri uri) {
+ super();
+ mContext = context;
+ mUri = uri;
+ // TODO: Check if uri points at a Slice?
+ }
+
+ @Override
+ protected void onActive() {
+ AsyncTask.execute(this::updateSlice);
+ mContext.getContentResolver().registerContentObserver(mUri, false, mObserver);
+ }
+
+ @Override
+ protected void onInactive() {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ private void updateSlice() {
+ postValue(Slice.bindSlice(mContext.getContentResolver(), mUri));
+ }
+
+ private final ContentObserver mObserver = new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ AsyncTask.execute(SliceLiveDataImpl.this::updateSlice);
+ }
+ };
+ }
+}
diff --git a/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
index c35dc2c..6e12dba 100644
--- a/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
+++ b/slices/view/src/main/java/androidx/app/slice/widget/SliceView.java
@@ -18,19 +18,14 @@
import android.app.slice.Slice;
import android.app.slice.SliceItem;
+import android.arch.lifecycle.Observer;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
import android.support.annotation.IntDef;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
-import android.support.v4.util.Preconditions;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -62,20 +57,17 @@
* {@link Slice#HINT_TITLE} would be placed in the title position of a template. A slice annotated
* with {@link Slice#HINT_LIST} would present the child items of that slice in a list.
* <p>
- * SliceView can be provided a slice via a uri {@link #setSlice(Uri)} in which case a content
- * observer will be set for that uri and the view will update if there are any changes to the slice.
- * To use this the app must have a special permission to bind to the slice (see
- * {@link android.Manifest.permission#BIND_SLICE}).
- * <p>
* Example usage:
*
* <pre class="prettyprint">
* SliceView v = new SliceView(getContext());
* v.setMode(desiredMode);
- * v.setSlice(sliceUri);
+ * LiveData<Slice> liveData = SliceLiveData.fromUri(sliceUri);
+ * liveData.observe(lifecycleOwner, v);
* </pre>
+ * @see SliceLiveData
*/
-public class SliceView extends ViewGroup {
+public class SliceView extends ViewGroup implements Observer<Slice> {
private static final String TAG = "SliceView";
@@ -136,7 +128,6 @@
private Slice mCurrentSlice;
private boolean mShowActions = true;
private boolean mIsScrollable;
- private SliceObserver mObserver;
private final int mShortcutSize;
public SliceView(Context context) {
@@ -153,7 +144,6 @@
public SliceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
mActions = new ActionRow(getContext(), true);
mActions.setBackground(new ColorDrawable(0xffeeeeee));
mCurrentView = new LargeTemplateView(getContext());
@@ -192,81 +182,23 @@
}
}
- /**
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- public void showSlice(Intent intent) {
- // TODO
- }
-
- /**
- * Populates this view with the {@link Slice} associated with the provided {@link Uri}. 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 when the slice identified by the provided URI 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 the a slice was found for the provided uri.
- * @see #clearSlice
- */
- public boolean setSlice(@NonNull Uri sliceUri) {
- Preconditions.checkNotNull(sliceUri,
- "Uri cannot be null, to remove the slice use clearSlice()");
- if (sliceUri == null) {
- clearSlice();
- return false;
- }
- validate(sliceUri);
- Slice s = Slice.bindSlice(getContext().getContentResolver(), sliceUri);
- if (s != null) {
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- }
- mObserver = new SliceObserver(new Handler(Looper.getMainLooper()));
- if (isAttachedToWindow()) {
- registerSlice(sliceUri);
- }
- mCurrentSlice = s;
- reinflate();
- }
- return s != null;
+ @Override
+ public void onChanged(@Nullable Slice slice) {
+ setSlice(slice);
}
/**
* Populates this view to the provided {@link Slice}.
- * <p>
- * This does not register a content observer on the URI that the slice is backed by so it will
- * not update if the content changes. To have the view update when the content changes use
- * {@link #setSlice(Uri)} instead. Unlike {@link #setSlice(Uri)}, this method does not require
- * any special permissions.
+ *
+ * This will not update automatically if the slice content changes, for live
+ * content see {@link SliceLiveData}.
*/
- public void showSlice(@NonNull Slice slice) {
- Preconditions.checkNotNull(slice,
- "Slice cannot be null, to remove the slice use clearSlice()");
- clearSlice();
+ public void setSlice(@Nullable Slice slice) {
mCurrentSlice = slice;
reinflate();
}
/**
- * Unregisters the change observer that is set when using {@link #setSlice}. Normally this is
- * done automatically during {@link #onDetachedFromWindow()}.
- * <p>
- * It is safe to call this method multiple times.
- */
- public void clearSlice() {
- mCurrentSlice = null;
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- mObserver = null;
- }
- }
-
- /**
* Set the mode this view should present in.
*/
public void setMode(@SliceMode int mode) {
@@ -324,29 +256,6 @@
return new LargeTemplateView(getContext());
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- registerSlice(mCurrentSlice != null ? mCurrentSlice.getUri() : null);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mObserver != null) {
- getContext().getContentResolver().unregisterContentObserver(mObserver);
- mObserver = null;
- }
- }
-
- private void registerSlice(Uri sliceUri) {
- if (sliceUri == null || mObserver == null) {
- return;
- }
- getContext().getContentResolver().registerContentObserver(sliceUri,
- false /* notifyForDescendants */, mObserver);
- }
-
private void reinflate() {
if (mCurrentSlice == null) {
return;
@@ -400,22 +309,4 @@
throw new RuntimeException("Invalid uri " + sliceUri);
}
}
-
- private class SliceObserver extends ContentObserver {
- SliceObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- this.onChange(selfChange, null);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- Slice s = Slice.bindSlice(getContext().getContentResolver(), uri);
- mCurrentSlice = s;
- reinflate();
- }
- }
}