It's only relevant when the {@link AssistStructure} is used for autofill purposes,
- * not for assist purposes.
- */
- public int getMinTextEms() {
- return mMinEms;
- }
-
- /**
- * Returns the maximum width in ems of the text associated with this node, or {@code -1}
- * if not supported by the node.
- *
- * It's only relevant when the {@link AssistStructure} is used for autofill purposes,
- * not for assist purposes.
- */
- public int getMaxTextEms() {
- return mMaxEms;
- }
-
- /**
- * Returns the maximum length of the text associated with this node node, or {@code -1}
- * if not supported by the node or not set.
- *
- *
It's only relevant when the {@link AssistStructure} is used for autofill purposes,
- * not for assist purposes.
- */
- public int getMaxTextLength() {
- return mMaxLength;
- }
}
/**
@@ -1817,21 +1775,6 @@ public class AssistStructure implements Parcelable {
mNode.mInputType = inputType;
}
- @Override
- public void setMinTextEms(int minEms) {
- mNode.mMinEms = minEms;
- }
-
- @Override
- public void setMaxTextEms(int maxEms) {
- mNode.mMaxEms = maxEms;
- }
-
- @Override
- public void setMaxTextLength(int maxLength) {
- mNode.mMaxLength = maxLength;
- }
-
@Override
public void setDataIsSensitive(boolean sensitive) {
mNode.mSanitized = !sensitive;
diff --git a/android/app/job/JobScheduler.java b/android/app/job/JobScheduler.java
index 0deb2e13..3868439f 100644
--- a/android/app/job/JobScheduler.java
+++ b/android/app/job/JobScheduler.java
@@ -24,6 +24,7 @@ import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.ClipData;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -39,18 +40,16 @@ import java.util.List;
* and how to construct them. You will construct these JobInfo objects and pass them to the
* JobScheduler with {@link #schedule(JobInfo)}. When the criteria declared are met, the
* system will execute this job on your application's {@link android.app.job.JobService}.
- * You identify the service component that implements the logic for your job when you
- * construct the JobInfo using
+ * You identify which JobService is meant to execute the logic for your job when you create the
+ * JobInfo with
* {@link android.app.job.JobInfo.Builder#JobInfo.Builder(int,android.content.ComponentName)}.
*
*
- * The framework will be intelligent about when it executes jobs, and attempt to batch
- * and defer them as much as possible. Typically if you don't specify a deadline on a job, it
- * can be run at any moment depending on the current state of the JobScheduler's internal queue.
- *
- * While a job is running, the system holds a wakelock on behalf of your app. For this reason,
- * you do not need to take any action to guarantee that the device stays awake for the
- * duration of the job.
+ * The framework will be intelligent about when you receive your callbacks, and attempt to batch
+ * and defer them as much as possible. Typically if you don't specify a deadline on your job, it
+ * can be run at any moment depending on the current state of the JobScheduler's internal queue,
+ * however it might be deferred as long as until the next time the device is connected to a power
+ * source.
*
* You do not
* instantiate this class directly; instead, retrieve it through
@@ -142,34 +141,30 @@ public abstract class JobScheduler {
int userId, String tag);
/**
- * Cancel the specified job. If the job is currently executing, it is stopped
- * immediately and the return value from its {@link JobService#onStopJob(JobParameters)}
- * method is ignored.
- *
- * @param jobId unique identifier for the job to be canceled, as supplied to
- * {@link JobInfo.Builder#JobInfo.Builder(int, android.content.ComponentName)
- * JobInfo.Builder(int, android.content.ComponentName)}.
+ * Cancel a job that is pending in the JobScheduler.
+ * @param jobId unique identifier for this job. Obtain this value from the jobs returned by
+ * {@link #getAllPendingJobs()}.
*/
public abstract void cancel(int jobId);
/**
- * Cancel all jobs that have been scheduled by the calling application.
+ * Cancel all jobs that have been registered with the JobScheduler by this package.
*/
public abstract void cancelAll();
/**
- * Retrieve all jobs that have been scheduled by the calling application.
+ * Retrieve all jobs for this package that are pending in the JobScheduler.
*
- * @return a list of all of the app's scheduled jobs. This includes jobs that are
- * currently started as well as those that are still waiting to run.
+ * @return a list of all the jobs registered by this package that have not
+ * yet been executed.
*/
public abstract @NonNull List getAllPendingJobs();
/**
- * Look up the description of a scheduled job.
+ * Retrieve a specific job for this package that is pending in the
+ * JobScheduler.
*
- * @return The {@link JobInfo} description of the given scheduled job, or {@code null}
- * if the supplied job ID does not correspond to any job.
+ * @return job registered by this package that has not yet been executed.
*/
public abstract @Nullable JobInfo getPendingJob(int jobId);
}
diff --git a/android/app/job/JobService.java b/android/app/job/JobService.java
index 69afed20..9096b47b 100644
--- a/android/app/job/JobService.java
+++ b/android/app/job/JobService.java
@@ -18,7 +18,16 @@ package android.app.job;
import android.app.Service;
import android.content.Intent;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
/**
* Entry point for the callback from the {@link android.app.job.JobScheduler}.
@@ -46,7 +55,7 @@ public abstract class JobService extends Service {
*
*
* If a job service is declared in the manifest but not protected with this
- * permission, that service will be ignored by the system.
+ * permission, that service will be ignored by the OS.
*/
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
@@ -72,36 +81,14 @@ public abstract class JobService extends Service {
}
/**
- * Called to indicate that the job has begun executing. Override this method with the
- * logic for your job. Like all other component lifecycle callbacks, this method executes
- * on your application's main thread.
- *
- * Return {@code true} from this method if your job needs to continue running. If you
- * do this, the job remains active until you call
- * {@link #jobFinished(JobParameters, boolean)} to tell the system that it has completed
- * its work, or until the job's required constraints are no longer satisfied. For
- * example, if the job was scheduled using
- * {@link JobInfo.Builder#setRequiresCharging(boolean) setRequiresCharging(true)},
- * it will be immediately halted by the system if the user unplugs the device from power,
- * the job's {@link #onStopJob(JobParameters)} callback will be invoked, and the app
- * will be expected to shut down all ongoing work connected with that job.
- *
- * The system holds a wakelock on behalf of your app as long as your job is executing.
- * This wakelock is acquired before this method is invoked, and is not released until either
- * you call {@link #jobFinished(JobParameters, boolean)}, or after the system invokes
- * {@link #onStopJob(JobParameters)} to notify your job that it is being shut down
- * prematurely.
- *
- * Returning {@code false} from this method means your job is already finished. The
- * system's wakelock for the job will be released, and {@link #onStopJob(JobParameters)}
- * will not be invoked.
+ * Override this method with the callback logic for your job. Any such logic needs to be
+ * performed on a separate thread, as this function is executed on your application's main
+ * thread.
*
- * @param params Parameters specifying info about this job, including the optional
- * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle).
- * This object serves to identify this specific running job instance when calling
- * {@link #jobFinished(JobParameters, boolean)}.
- * @return {@code true} if your service will continue running, using a separate thread
- * when appropriate. {@code false} means that this job has completed its work.
+ * @param params Parameters specifying info about this job, including the extras bundle you
+ * optionally provided at job-creation time.
+ * @return True if your service needs to process the work (on a separate thread). False if
+ * there's no more work to be done for this job.
*/
public abstract boolean onStartJob(JobParameters params);
@@ -114,44 +101,37 @@ public abstract class JobService extends Service {
* {@link android.app.job.JobInfo.Builder#setRequiredNetworkType(int)}, yet while your
* job was executing the user toggled WiFi. Another example is if you had specified
* {@link android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
- * idle maintenance window. You are solely responsible for the behavior of your application
- * upon receipt of this message; your app will likely start to misbehave if you ignore it.
- *
- * Once this method returns, the system releases the wakelock that it is holding on
- * behalf of the job.
+ * idle maintenance window. You are solely responsible for the behaviour of your application
+ * upon receipt of this message; your app will likely start to misbehave if you ignore it. One
+ * immediate repercussion is that the system will cease holding a wakelock for you.
*
- * @param params The parameters identifying this job, as supplied to
- * the job in the {@link #onStartJob(JobParameters)} callback.
- * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
- * this job based on the retry criteria provided at job creation-time; or {@code false}
- * to end the job entirely. Regardless of the value returned, your job must stop executing.
+ * @param params Parameters specifying info about this job.
+ * @return True to indicate to the JobManager whether you'd like to reschedule this job based
+ * on the retry criteria provided at job creation-time. False to drop the job. Regardless of
+ * the value returned, your job must stop executing.
*/
public abstract boolean onStopJob(JobParameters params);
/**
- * Call this to inform the JobScheduler that the job has finished its work. When the
- * system receives this message, it releases the wakelock being held for the job.
+ * Call this to inform the JobManager you've finished executing. This can be called from any
+ * thread, as it will ultimately be run on your application's main thread. When the system
+ * receives this message it will release the wakelock being held.
*
- * You can request that the job be scheduled again by passing {@code true} as
- * the wantsReschedule
parameter. This will apply back-off policy
- * for the job; this policy can be adjusted through the
- * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)} method
- * when the job is originally scheduled. The job's initial
- * requirements are preserved when jobs are rescheduled, regardless of backed-off
- * policy.
- *
- * A job running while the device is dozing will not be rescheduled with the normal back-off
- * policy. Instead, the job will be re-added to the queue and executed again during
- * a future idle maintenance window.
+ * You can specify post-execution behaviour to the scheduler here with
+ * needsReschedule
. This will apply a back-off timer to your job based on
+ * the default, or what was set with
+ * {@link android.app.job.JobInfo.Builder#setBackoffCriteria(long, int)}. The original
+ * requirements are always honoured even for a backed-off job. Note that a job running in
+ * idle mode will not be backed-off. Instead what will happen is the job will be re-added
+ * to the queue and re-executed within a future idle maintenance window.
*
*
- * @param params The parameters identifying this job, as supplied to
- * the job in the {@link #onStartJob(JobParameters)} callback.
- * @param wantsReschedule {@code true} if this job should be rescheduled according
- * to the back-off criteria specified when it was first scheduled; {@code false}
- * otherwise.
+ * @param params Parameters specifying system-provided info about this job, this was given to
+ * your application in {@link #onStartJob(JobParameters)}.
+ * @param needsReschedule True if this job should be rescheduled according to the back-off
+ * criteria specified at schedule-time. False otherwise.
*/
- public final void jobFinished(JobParameters params, boolean wantsReschedule) {
- mEngine.jobFinished(params, wantsReschedule);
+ public final void jobFinished(JobParameters params, boolean needsReschedule) {
+ mEngine.jobFinished(params, needsReschedule);
}
-}
+}
\ No newline at end of file
diff --git a/android/app/slice/Slice.java b/android/app/slice/Slice.java
deleted file mode 100644
index 7f9f74b4..00000000
--- a/android/app/slice/Slice.java
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringDef;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.ContentResolver;
-import android.content.IContentProvider;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.widget.RemoteViews;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * A slice is a piece of app content and actions that can be surfaced outside of the app.
- *
- * They are constructed using {@link Builder} in a tree structure
- * that provides the OS some information about how the content should be displayed.
- */
-public final class Slice implements Parcelable {
-
- /**
- * @hide
- */
- @StringDef({HINT_TITLE, HINT_LIST, HINT_LIST_ITEM, HINT_LARGE, HINT_ACTIONS, HINT_SELECTED,
- HINT_SOURCE, HINT_MESSAGE, HINT_HORIZONTAL, HINT_NO_TINT, HINT_PARTIAL})
- public @interface SliceHint{ }
-
- /**
- * Hint that this content is a title of other content in the slice.
- */
- public static final String HINT_TITLE = "title";
- /**
- * Hint that all sub-items/sub-slices within this content should be considered
- * to have {@link #HINT_LIST_ITEM}.
- */
- public static final String HINT_LIST = "list";
- /**
- * Hint that this item is part of a list and should be formatted as if is part
- * of a list.
- */
- public static final String HINT_LIST_ITEM = "list_item";
- /**
- * Hint that this content is important and should be larger when displayed if
- * possible.
- */
- public static final String HINT_LARGE = "large";
- /**
- * Hint that this slice contains a number of actions that can be grouped together
- * in a sort of controls area of the UI.
- */
- public static final String HINT_ACTIONS = "actions";
- /**
- * Hint indicating that this item (and its sub-items) are the current selection.
- */
- public static final String HINT_SELECTED = "selected";
- /**
- * Hint to indicate that this is a message as part of a communication
- * sequence in this slice.
- */
- public static final String HINT_MESSAGE = "message";
- /**
- * Hint to tag the source (i.e. sender) of a {@link #HINT_MESSAGE}.
- */
- public static final String HINT_SOURCE = "source";
- /**
- * Hint that list items within this slice or subslice would appear better
- * if organized horizontally.
- */
- public static final String HINT_HORIZONTAL = "horizontal";
- /**
- * Hint to indicate that this content should not be tinted.
- */
- public static final String HINT_NO_TINT = "no_tint";
- /**
- * Hint to indicate that this slice is incomplete and an update will be sent once
- * loading is complete. Slices which contain HINT_PARTIAL will not be cached by the
- * OS and should not be cached by apps.
- */
- public static final String HINT_PARTIAL = "partial";
-
- // These two are coming over from prototyping, but we probably don't want in
- // public API, at least not right now.
- /**
- * @hide
- */
- public static final String HINT_ALT = "alt";
-
- private final SliceItem[] mItems;
- private final @SliceHint String[] mHints;
- private Uri mUri;
-
- Slice(ArrayList items, @SliceHint String[] hints, Uri uri) {
- mHints = hints;
- mItems = items.toArray(new SliceItem[items.size()]);
- mUri = uri;
- }
-
- protected Slice(Parcel in) {
- mHints = in.readStringArray();
- int n = in.readInt();
- mItems = new SliceItem[n];
- for (int i = 0; i < n; i++) {
- mItems[i] = SliceItem.CREATOR.createFromParcel(in);
- }
- mUri = Uri.CREATOR.createFromParcel(in);
- }
-
- /**
- * @return The Uri that this Slice represents.
- */
- public Uri getUri() {
- return mUri;
- }
-
- /**
- * @return All child {@link SliceItem}s that this Slice contains.
- */
- public List getItems() {
- return Arrays.asList(mItems);
- }
-
- /**
- * @return All hints associated with this Slice.
- */
- public @SliceHint List getHints() {
- return Arrays.asList(mHints);
- }
-
- /**
- * @hide
- */
- public SliceItem getPrimaryIcon() {
- for (SliceItem item : getItems()) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
- return item;
- }
- if (!(item.getType() == SliceItem.TYPE_SLICE && item.hasHint(Slice.HINT_LIST))
- && !item.hasHint(Slice.HINT_ACTIONS)
- && !item.hasHint(Slice.HINT_LIST_ITEM)
- && (item.getType() != SliceItem.TYPE_ACTION)) {
- SliceItem icon = SliceQuery.find(item, SliceItem.TYPE_IMAGE);
- if (icon != null) return icon;
- }
- }
- return null;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStringArray(mHints);
- dest.writeInt(mItems.length);
- for (int i = 0; i < mItems.length; i++) {
- mItems[i].writeToParcel(dest, flags);
- }
- mUri.writeToParcel(dest, 0);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- /**
- * @hide
- */
- public boolean hasHint(@SliceHint String hint) {
- return ArrayUtils.contains(mHints, hint);
- }
-
- /**
- * A Builder used to construct {@link Slice}s
- */
- public static class Builder {
-
- private final Uri mUri;
- private ArrayList mItems = new ArrayList<>();
- private @SliceHint ArrayList mHints = new ArrayList<>();
-
- /**
- * Create a builder which will construct a {@link Slice} for the Given Uri.
- * @param uri Uri to tag for this slice.
- */
- public Builder(@NonNull Uri uri) {
- mUri = uri;
- }
-
- /**
- * Create a builder for a {@link Slice} that is a sub-slice of the slice
- * being constructed by the provided builder.
- * @param parent The builder constructing the parent slice
- */
- public Builder(@NonNull Slice.Builder parent) {
- mUri = parent.mUri.buildUpon().appendPath("_gen")
- .appendPath(String.valueOf(mItems.size())).build();
- }
-
- /**
- * Add hints to the Slice being constructed
- */
- public Builder addHints(@SliceHint String... hints) {
- mHints.addAll(Arrays.asList(hints));
- return this;
- }
-
- /**
- * Add hints to the Slice being constructed
- */
- public Builder addHints(@SliceHint List hints) {
- return addHints(hints.toArray(new String[hints.size()]));
- }
-
- /**
- * Add a sub-slice to the slice being constructed
- */
- public Builder addSubSlice(@NonNull Slice slice) {
- mItems.add(new SliceItem(slice, SliceItem.TYPE_SLICE, slice.getHints().toArray(
- new String[slice.getHints().size()])));
- return this;
- }
-
- /**
- * Add an action to the slice being constructed
- */
- public Slice.Builder addAction(@NonNull PendingIntent action, @NonNull Slice s) {
- mItems.add(new SliceItem(action, s, SliceItem.TYPE_ACTION, new String[0]));
- return this;
- }
-
- /**
- * Add text to the slice being constructed
- */
- public Builder addText(CharSequence text, @SliceHint String... hints) {
- mItems.add(new SliceItem(text, SliceItem.TYPE_TEXT, hints));
- return this;
- }
-
- /**
- * Add text to the slice being constructed
- */
- public Builder addText(CharSequence text, @SliceHint List hints) {
- return addText(text, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * Add an image to the slice being constructed
- */
- public Builder addIcon(Icon icon, @SliceHint String... hints) {
- mItems.add(new SliceItem(icon, SliceItem.TYPE_IMAGE, hints));
- return this;
- }
-
- /**
- * Add an image to the slice being constructed
- */
- public Builder addIcon(Icon icon, @SliceHint List hints) {
- return addIcon(icon, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * @hide This isn't final
- */
- public Builder addRemoteView(RemoteViews remoteView, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteView, SliceItem.TYPE_REMOTE_VIEW, hints));
- return this;
- }
-
- /**
- * Add remote input to the slice being constructed
- */
- public Slice.Builder addRemoteInput(RemoteInput remoteInput,
- @SliceHint List hints) {
- return addRemoteInput(remoteInput, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * Add remote input to the slice being constructed
- */
- public Slice.Builder addRemoteInput(RemoteInput remoteInput, @SliceHint String... hints) {
- mItems.add(new SliceItem(remoteInput, SliceItem.TYPE_REMOTE_INPUT, hints));
- return this;
- }
-
- /**
- * Add a color to the slice being constructed
- */
- public Builder addColor(int color, @SliceHint String... hints) {
- mItems.add(new SliceItem(color, SliceItem.TYPE_COLOR, hints));
- return this;
- }
-
- /**
- * Add a color to the slice being constructed
- */
- public Builder addColor(int color, @SliceHint List hints) {
- return addColor(color, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * Add a timestamp to the slice being constructed
- */
- public Slice.Builder addTimestamp(long time, @SliceHint String... hints) {
- mItems.add(new SliceItem(time, SliceItem.TYPE_TIMESTAMP, hints));
- return this;
- }
-
- /**
- * Add a timestamp to the slice being constructed
- */
- public Slice.Builder addTimestamp(long time, @SliceHint List hints) {
- return addTimestamp(time, hints.toArray(new String[hints.size()]));
- }
-
- /**
- * Construct the slice.
- */
- public Slice build() {
- return new Slice(mItems, mHints.toArray(new String[mHints.size()]), mUri);
- }
- }
-
- public static final Creator CREATOR = new Creator() {
- @Override
- public Slice createFromParcel(Parcel in) {
- return new Slice(in);
- }
-
- @Override
- public Slice[] newArray(int size) {
- return new Slice[size];
- }
- };
-
- /**
- * @hide
- * @return A string representation of this slice.
- */
- public String toString() {
- return toString("");
- }
-
- private String toString(String indent) {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mItems.length; i++) {
- sb.append(indent);
- if (mItems[i].getType() == SliceItem.TYPE_SLICE) {
- sb.append("slice:\n");
- sb.append(mItems[i].getSlice().toString(indent + " "));
- } else if (mItems[i].getType() == SliceItem.TYPE_TEXT) {
- sb.append("text: ");
- sb.append(mItems[i].getText());
- sb.append("\n");
- } else {
- sb.append(SliceItem.typeToString(mItems[i].getType()));
- sb.append("\n");
- }
- }
- return sb.toString();
- }
-
- /**
- * Turns a slice Uri into slice content.
- *
- * @param resolver ContentResolver to be used.
- * @param uri The URI to a slice provider
- * @return The Slice provided by the app or null if none is given.
- * @see Slice
- */
- public static @Nullable Slice bindSlice(ContentResolver resolver, @NonNull Uri uri) {
- Preconditions.checkNotNull(uri, "uri");
- IContentProvider provider = resolver.acquireProvider(uri);
- if (provider == null) {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
- try {
- Bundle extras = new Bundle();
- extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
- final Bundle res = provider.call(resolver.getPackageName(), SliceProvider.METHOD_SLICE,
- null, extras);
- Bundle.setDefusable(res, true);
- 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);
- }
- }
-}
diff --git a/android/app/slice/SliceItem.java b/android/app/slice/SliceItem.java
deleted file mode 100644
index 6e69b051..00000000
--- a/android/app/slice/SliceItem.java
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.graphics.drawable.Icon;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.util.Pair;
-import android.widget.RemoteViews;
-
-import com.android.internal.util.ArrayUtils;
-
-import java.util.Arrays;
-import java.util.List;
-
-
-/**
- * A SliceItem is a single unit in the tree structure of a {@link Slice}.
- *
- * A SliceItem a piece of content and some hints about what that content
- * means or how it should be displayed. The types of content can be:
- * {@link #TYPE_SLICE}
- * {@link #TYPE_TEXT}
- * {@link #TYPE_IMAGE}
- * {@link #TYPE_ACTION}
- * {@link #TYPE_COLOR}
- * {@link #TYPE_TIMESTAMP}
- * {@link #TYPE_REMOTE_INPUT}
- *
- * The hints that a {@link SliceItem} are a set of strings which annotate
- * the content. The hints that are guaranteed to be understood by the system
- * are defined on {@link Slice}.
- */
-public final class SliceItem implements Parcelable {
-
- /**
- * @hide
- */
- @IntDef({TYPE_SLICE, TYPE_TEXT, TYPE_IMAGE, TYPE_ACTION, TYPE_COLOR,
- TYPE_TIMESTAMP, TYPE_REMOTE_INPUT})
- public @interface SliceType {}
-
- /**
- * A {@link SliceItem} that contains a {@link Slice}
- */
- public static final int TYPE_SLICE = 1;
- /**
- * A {@link SliceItem} that contains a {@link CharSequence}
- */
- public static final int TYPE_TEXT = 2;
- /**
- * A {@link SliceItem} that contains an {@link Icon}
- */
- public static final int TYPE_IMAGE = 3;
- /**
- * A {@link SliceItem} that contains a {@link PendingIntent}
- *
- * Note: Actions contain 2 pieces of data, In addition to the pending intent, the
- * item contains a {@link Slice} that the action applies to.
- */
- public static final int TYPE_ACTION = 4;
- /**
- * @hide This isn't final
- */
- public static final int TYPE_REMOTE_VIEW = 5;
- /**
- * A {@link SliceItem} that contains a Color int.
- */
- public static final int TYPE_COLOR = 6;
- /**
- * A {@link SliceItem} that contains a timestamp.
- */
- public static final int TYPE_TIMESTAMP = 8;
- /**
- * A {@link SliceItem} that contains a {@link RemoteInput}.
- */
- public static final int TYPE_REMOTE_INPUT = 9;
-
- /**
- * @hide
- */
- protected @Slice.SliceHint
- String[] mHints;
- private final int mType;
- private final Object mObj;
-
- /**
- * @hide
- */
- public SliceItem(Object obj, @SliceType int type, @Slice.SliceHint String[] hints) {
- mHints = hints;
- mType = type;
- mObj = obj;
- }
-
- /**
- * @hide
- */
- public SliceItem(PendingIntent intent, Slice slice, int type, @Slice.SliceHint String[] hints) {
- this(new Pair<>(intent, slice), type, hints);
- }
-
- /**
- * Gets all hints associated with this SliceItem.
- * @return Array of hints.
- */
- public @NonNull @Slice.SliceHint List getHints() {
- return Arrays.asList(mHints);
- }
-
- /**
- * @hide
- */
- public void addHint(@Slice.SliceHint String hint) {
- mHints = ArrayUtils.appendElement(String.class, mHints, hint);
- }
-
- /**
- * @hide
- */
- public void removeHint(String hint) {
- ArrayUtils.removeElement(String.class, mHints, hint);
- }
-
- public @SliceType int getType() {
- return mType;
- }
-
- /**
- * @return The text held by this {@link #TYPE_TEXT} SliceItem
- */
- public CharSequence getText() {
- return (CharSequence) mObj;
- }
-
- /**
- * @return The icon held by this {@link #TYPE_IMAGE} SliceItem
- */
- public Icon getIcon() {
- return (Icon) mObj;
- }
-
- /**
- * @return The pending intent held by this {@link #TYPE_ACTION} SliceItem
- */
- public PendingIntent getAction() {
- return ((Pair) mObj).first;
- }
-
- /**
- * @hide This isn't final
- */
- public RemoteViews getRemoteView() {
- return (RemoteViews) mObj;
- }
-
- /**
- * @return The remote input held by this {@link #TYPE_REMOTE_INPUT} SliceItem
- */
- public RemoteInput getRemoteInput() {
- return (RemoteInput) mObj;
- }
-
- /**
- * @return The color held by this {@link #TYPE_COLOR} SliceItem
- */
- public int getColor() {
- return (Integer) mObj;
- }
-
- /**
- * @return The slice held by this {@link #TYPE_ACTION} or {@link #TYPE_SLICE} SliceItem
- */
- public Slice getSlice() {
- if (getType() == TYPE_ACTION) {
- return ((Pair) mObj).second;
- }
- return (Slice) mObj;
- }
-
- /**
- * @return The timestamp held by this {@link #TYPE_TIMESTAMP} SliceItem
- */
- public long getTimestamp() {
- return (Long) mObj;
- }
-
- /**
- * @param hint The hint to check for
- * @return true if this item contains the given hint
- */
- public boolean hasHint(@Slice.SliceHint String hint) {
- return ArrayUtils.contains(mHints, hint);
- }
-
- /**
- * @hide
- */
- public SliceItem(Parcel in) {
- mHints = in.readStringArray();
- mType = in.readInt();
- mObj = readObj(mType, in);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeStringArray(mHints);
- dest.writeInt(mType);
- writeObj(dest, flags, mObj, mType);
- }
-
- /**
- * @hide
- */
- public boolean hasHints(@Slice.SliceHint String[] hints) {
- if (hints == null) return true;
- for (String hint : hints) {
- if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * @hide
- */
- public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
- if (hints == null) return false;
- for (String hint : hints) {
- if (ArrayUtils.contains(mHints, hint)) {
- return true;
- }
- }
- return false;
- }
-
- private void writeObj(Parcel dest, int flags, Object obj, int type) {
- switch (type) {
- case TYPE_SLICE:
- case TYPE_REMOTE_VIEW:
- case TYPE_IMAGE:
- case TYPE_REMOTE_INPUT:
- ((Parcelable) obj).writeToParcel(dest, flags);
- break;
- case TYPE_ACTION:
- ((Pair) obj).first.writeToParcel(dest, flags);
- ((Pair) obj).second.writeToParcel(dest, flags);
- break;
- case TYPE_TEXT:
- TextUtils.writeToParcel((CharSequence) mObj, dest, flags);
- break;
- case TYPE_COLOR:
- dest.writeInt((Integer) mObj);
- break;
- case TYPE_TIMESTAMP:
- dest.writeLong((Long) mObj);
- break;
- }
- }
-
- private static Object readObj(int type, Parcel in) {
- switch (type) {
- case TYPE_SLICE:
- return Slice.CREATOR.createFromParcel(in);
- case TYPE_TEXT:
- return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- case TYPE_IMAGE:
- return Icon.CREATOR.createFromParcel(in);
- case TYPE_ACTION:
- return new Pair(
- PendingIntent.CREATOR.createFromParcel(in),
- Slice.CREATOR.createFromParcel(in));
- case TYPE_REMOTE_VIEW:
- return RemoteViews.CREATOR.createFromParcel(in);
- case TYPE_COLOR:
- return in.readInt();
- case TYPE_TIMESTAMP:
- return in.readLong();
- case TYPE_REMOTE_INPUT:
- return RemoteInput.CREATOR.createFromParcel(in);
- }
- throw new RuntimeException("Unsupported type " + type);
- }
-
- public static final Creator CREATOR = new Creator() {
- @Override
- public SliceItem createFromParcel(Parcel in) {
- return new SliceItem(in);
- }
-
- @Override
- public SliceItem[] newArray(int size) {
- return new SliceItem[size];
- }
- };
-
- /**
- * @hide
- */
- public static String typeToString(int type) {
- switch (type) {
- case TYPE_SLICE:
- return "Slice";
- case TYPE_TEXT:
- return "Text";
- case TYPE_IMAGE:
- return "Image";
- case TYPE_ACTION:
- return "Action";
- case TYPE_REMOTE_VIEW:
- return "RemoteView";
- case TYPE_COLOR:
- return "Color";
- case TYPE_TIMESTAMP:
- return "Timestamp";
- case TYPE_REMOTE_INPUT:
- return "RemoteInput";
- }
- return "Unrecognized type: " + type;
- }
-}
diff --git a/android/app/slice/SliceProvider.java b/android/app/slice/SliceProvider.java
deleted file mode 100644
index df87b455..00000000
--- a/android/app/slice/SliceProvider.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice;
-
-import android.Manifest.permission;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.CancellationSignal;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.StrictMode;
-import android.os.StrictMode.ThreadPolicy;
-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.
- *
- * 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.
- *
- * 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.
- *
- * {@literal
- * }
- *
- *
- * @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}.
- */
- public static final String SLICE_TYPE = "vnd.android.slice";
-
- private static final String TAG = "SliceProvider";
- /**
- * @hide
- */
- public static final String EXTRA_BIND_URI = "slice_uri";
- /**
- * @hide
- */
- public static final String METHOD_SLICE = "bind_slice";
- /**
- * @hide
- */
- public static final String EXTRA_SLICE = "slice";
-
- private static final boolean DEBUG = false;
-
- /**
- * Implemented to create a slice. Will be called on the main thread.
- *
- * onBindSlice should return as quickly as possible so that the UI tied
- * to this slice can be responsive. No network or other IO will be allowed
- * during onBindSlice. Any loading that needs to be done should happen
- * off the main thread with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)}
- * when the app is ready to provide the complete data in onBindSlice.
- *
- *
- * @see {@link Slice}.
- * @see {@link Slice#HINT_PARTIAL}
- */
- // TODO: Provide alternate notifyChange that takes in the slice (i.e. notifyChange(Uri, Slice)).
- public abstract Slice onBindSlice(Uri sliceUri);
-
- @Override
- public final int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- if (DEBUG) Log.d(TAG, "update " + uri);
- return 0;
- }
-
- @Override
- public final int delete(Uri uri, String selection, String[] selectionArgs) {
- if (DEBUG) Log.d(TAG, "delete " + uri);
- return 0;
- }
-
- @Override
- public final Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- if (DEBUG) Log.d(TAG, "query " + uri);
- return null;
- }
-
- @Override
- public final Cursor query(Uri uri, String[] projection, String selection, String[]
- selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
- if (DEBUG) Log.d(TAG, "query " + uri);
- return null;
- }
-
- @Override
- public final Cursor query(Uri uri, String[] projection, Bundle queryArgs,
- CancellationSignal cancellationSignal) {
- if (DEBUG) Log.d(TAG, "query " + uri);
- return null;
- }
-
- @Override
- public final Uri insert(Uri uri, ContentValues values) {
- if (DEBUG) Log.d(TAG, "insert " + uri);
- return null;
- }
-
- @Override
- public final String getType(Uri uri) {
- if (DEBUG) Log.d(TAG, "getType " + uri);
- return SLICE_TYPE;
- }
-
- @Override
- public Bundle call(String method, String arg, Bundle extras) {
- if (method.equals(METHOD_SLICE)) {
- getContext().enforceCallingPermission(permission.BIND_SLICE,
- "Slice binding requires the permission BIND_SLICE");
- Uri uri = extras.getParcelable(EXTRA_BIND_URI);
-
- Slice s = handleBindSlice(uri);
- Bundle b = new Bundle();
- b.putParcelable(EXTRA_SLICE, s);
- return b;
- }
- return super.call(method, arg, extras);
- }
-
- private Slice handleBindSlice(Uri sliceUri) {
- Slice[] output = new Slice[1];
- CountDownLatch latch = new CountDownLatch(1);
- Handler mainHandler = new Handler(Looper.getMainLooper());
- mainHandler.post(() -> {
- ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
- try {
- StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
- .detectAll()
- .penaltyDeath()
- .build());
- output[0] = onBindSlice(sliceUri);
- } finally {
- StrictMode.setThreadPolicy(oldPolicy);
- latch.countDown();
- }
- });
- try {
- latch.await();
- return output[0];
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-}
diff --git a/android/app/slice/SliceQuery.java b/android/app/slice/SliceQuery.java
deleted file mode 100644
index d1fe2c90..00000000
--- a/android/app/slice/SliceQuery.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-import java.util.Spliterators;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-/**
- * A bunch of utilities for searching the contents of a slice.
- * @hide
- */
-public class SliceQuery {
- private static final String TAG = "SliceQuery";
-
- /**
- * @hide
- */
- public static SliceItem findNotContaining(SliceItem container, List list) {
- SliceItem ret = null;
- while (ret == null && list.size() != 0) {
- SliceItem remove = list.remove(0);
- if (!contains(container, remove)) {
- ret = remove;
- }
- }
- return ret;
- }
-
- /**
- * @hide
- */
- private static boolean contains(SliceItem container, SliceItem item) {
- if (container == null || item == null) return false;
- return stream(container).filter(s -> (s == item)).findAny().isPresent();
- }
-
- /**
- * @hide
- */
- public static List findAll(SliceItem s, int type) {
- return findAll(s, type, (String[]) null, null);
- }
-
- /**
- * @hide
- */
- public static List findAll(SliceItem s, int type, String hints, String nonHints) {
- return findAll(s, type, new String[]{ hints }, new String[]{ nonHints });
- }
-
- /**
- * @hide
- */
- public static List findAll(SliceItem s, int type, String[] hints,
- String[] nonHints) {
- return stream(s).filter(item -> (type == -1 || item.getType() == type)
- && (item.hasHints(hints) && !item.hasAnyHints(nonHints)))
- .collect(Collectors.toList());
- }
-
- /**
- * @hide
- */
- public static SliceItem find(Slice s, int type, String hints, String nonHints) {
- return find(s, type, new String[]{ hints }, new String[]{ nonHints });
- }
-
- /**
- * @hide
- */
- public static SliceItem find(Slice s, int type) {
- return find(s, type, (String[]) null, null);
- }
-
- /**
- * @hide
- */
- public static SliceItem find(SliceItem s, int type) {
- return find(s, type, (String[]) null, null);
- }
-
- /**
- * @hide
- */
- public static SliceItem find(SliceItem s, int type, String hints, String nonHints) {
- return find(s, type, new String[]{ hints }, new String[]{ nonHints });
- }
-
- /**
- * @hide
- */
- public static SliceItem find(Slice s, int type, String[] hints, String[] nonHints) {
- List h = s.getHints();
- return find(new SliceItem(s, SliceItem.TYPE_SLICE, h.toArray(new String[h.size()])), type,
- hints, nonHints);
- }
-
- /**
- * @hide
- */
- public static SliceItem find(SliceItem s, int type, String[] hints, String[] nonHints) {
- return stream(s).filter(item -> (item.getType() == type || type == -1)
- && (item.hasHints(hints) && !item.hasAnyHints(nonHints))).findFirst().orElse(null);
- }
-
- /**
- * @hide
- */
- public static Stream stream(SliceItem slice) {
- Queue items = new LinkedList();
- items.add(slice);
- Iterator iterator = new Iterator() {
- @Override
- public boolean hasNext() {
- return items.size() != 0;
- }
-
- @Override
- public SliceItem next() {
- SliceItem item = items.poll();
- if (item.getType() == SliceItem.TYPE_SLICE
- || item.getType() == SliceItem.TYPE_ACTION) {
- items.addAll(item.getSlice().getItems());
- }
- return item;
- }
- };
- return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
- }
-}
diff --git a/android/app/slice/views/ActionRow.java b/android/app/slice/views/ActionRow.java
deleted file mode 100644
index c7d99f7f..00000000
--- a/android/app/slice/views/ActionRow.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteInput;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.drawable.Icon;
-import android.os.AsyncTask;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewParent;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * @hide
- */
-public class ActionRow extends FrameLayout {
-
- private static final int MAX_ACTIONS = 5;
- private final int mSize;
- private final int mIconPadding;
- private final LinearLayout mActionsGroup;
- private final boolean mFullActions;
- private int mColor = Color.BLACK;
-
- public ActionRow(Context context, boolean fullActions) {
- super(context);
- mFullActions = fullActions;
- mSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48,
- context.getResources().getDisplayMetrics());
- mIconPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12,
- context.getResources().getDisplayMetrics());
- mActionsGroup = new LinearLayout(context);
- mActionsGroup.setOrientation(LinearLayout.HORIZONTAL);
- mActionsGroup.setLayoutParams(
- new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- addView(mActionsGroup);
- }
-
- private void setColor(int color) {
- mColor = color;
- for (int i = 0; i < mActionsGroup.getChildCount(); i++) {
- View view = mActionsGroup.getChildAt(i);
- SliceItem item = (SliceItem) view.getTag();
- boolean tint = !item.hasHint(Slice.HINT_NO_TINT);
- if (tint) {
- ((ImageView) view).setImageTintList(ColorStateList.valueOf(mColor));
- }
- }
- }
-
- private ImageView addAction(Icon icon, boolean allowTint, SliceItem image) {
- ImageView imageView = new ImageView(getContext());
- imageView.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
- imageView.setScaleType(ScaleType.FIT_CENTER);
- imageView.setImageIcon(icon);
- if (allowTint) {
- imageView.setImageTintList(ColorStateList.valueOf(mColor));
- }
- imageView.setBackground(SliceViewUtil.getDrawable(getContext(),
- android.R.attr.selectableItemBackground));
- imageView.setTag(image);
- addAction(imageView);
- return imageView;
- }
-
- /**
- * Set the actions and color for this action row.
- */
- public void setActions(SliceItem actionRow, SliceItem defColor) {
- removeAllViews();
- mActionsGroup.removeAllViews();
- addView(mActionsGroup);
-
- SliceItem color = SliceQuery.find(actionRow, SliceItem.TYPE_COLOR);
- if (color == null) {
- color = defColor;
- }
- if (color != null) {
- setColor(color.getColor());
- }
- SliceQuery.findAll(actionRow, SliceItem.TYPE_ACTION).forEach(action -> {
- if (mActionsGroup.getChildCount() >= MAX_ACTIONS) {
- return;
- }
- SliceItem image = SliceQuery.find(action, SliceItem.TYPE_IMAGE);
- if (image == null) {
- return;
- }
- boolean tint = !image.hasHint(Slice.HINT_NO_TINT);
- SliceItem input = SliceQuery.find(action, SliceItem.TYPE_REMOTE_INPUT);
- if (input != null && input.getRemoteInput().getAllowFreeFormInput()) {
- addAction(image.getIcon(), tint, image).setOnClickListener(
- v -> handleRemoteInputClick(v, action.getAction(), input.getRemoteInput()));
- createRemoteInputView(mColor, getContext());
- } else {
- addAction(image.getIcon(), tint, image).setOnClickListener(v -> AsyncTask.execute(
- () -> {
- try {
- action.getAction().send();
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }));
- }
- });
- setVisibility(getChildCount() != 0 ? View.VISIBLE : View.GONE);
- }
-
- private void addAction(View child) {
- mActionsGroup.addView(child, new LinearLayout.LayoutParams(mSize, mSize, 1));
- }
-
- private void createRemoteInputView(int color, Context context) {
- View riv = RemoteInputView.inflate(context, this);
- riv.setVisibility(View.INVISIBLE);
- addView(riv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- riv.setBackgroundColor(color);
- }
-
- private boolean handleRemoteInputClick(View view, PendingIntent pendingIntent,
- RemoteInput input) {
- if (input == null) {
- return false;
- }
-
- ViewParent p = view.getParent().getParent();
- RemoteInputView riv = null;
- while (p != null) {
- if (p instanceof View) {
- View pv = (View) p;
- riv = findRemoteInputView(pv);
- if (riv != null) {
- break;
- }
- }
- p = p.getParent();
- }
- if (riv == null) {
- return false;
- }
-
- int width = view.getWidth();
- if (view instanceof TextView) {
- // Center the reveal on the text which might be off-center from the TextView
- TextView tv = (TextView) view;
- if (tv.getLayout() != null) {
- int innerWidth = (int) tv.getLayout().getLineWidth(0);
- innerWidth += tv.getCompoundPaddingLeft() + tv.getCompoundPaddingRight();
- width = Math.min(width, innerWidth);
- }
- }
- int cx = view.getLeft() + width / 2;
- int cy = view.getTop() + view.getHeight() / 2;
- int w = riv.getWidth();
- int h = riv.getHeight();
- int r = Math.max(
- Math.max(cx + cy, cx + (h - cy)),
- Math.max((w - cx) + cy, (w - cx) + (h - cy)));
-
- riv.setRevealParameters(cx, cy, r);
- riv.setPendingIntent(pendingIntent);
- riv.setRemoteInput(new RemoteInput[] {
- input
- }, input);
- riv.focusAnimated();
- return true;
- }
-
- private RemoteInputView findRemoteInputView(View v) {
- if (v == null) {
- return null;
- }
- return (RemoteInputView) v.findViewWithTag(RemoteInputView.VIEW_TAG);
- }
-}
diff --git a/android/app/slice/views/GridView.java b/android/app/slice/views/GridView.java
deleted file mode 100644
index 6f30c507..00000000
--- a/android/app/slice/views/GridView.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
-import android.content.Context;
-import android.graphics.Color;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public class GridView extends LinearLayout implements SliceListView {
-
- private static final String TAG = "GridView";
-
- private static final int MAX_IMAGES = 3;
- private static final int MAX_ALL = 5;
- private boolean mIsAllImages;
-
- public GridView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mIsAllImages) {
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = width / getChildCount();
- heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.EXACTLY,
- height);
- getLayoutParams().height = height;
- for (int i = 0; i < getChildCount(); i++) {
- getChildAt(i).getLayoutParams().height = height;
- }
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- mIsAllImages = true;
- removeAllViews();
- int total = 1;
- if (slice.getType() == SliceItem.TYPE_SLICE) {
- List items = slice.getSlice().getItems();
- total = items.size();
- for (int i = 0; i < total; i++) {
- SliceItem item = items.get(i);
- if (isFull()) {
- continue;
- }
- if (!addItem(item)) {
- mIsAllImages = false;
- }
- }
- } else {
- if (!isFull()) {
- if (!addItem(slice)) {
- mIsAllImages = false;
- }
- }
- }
- if (total > getChildCount() && mIsAllImages) {
- addExtraCount(total - getChildCount());
- }
- }
-
- private void addExtraCount(int numExtra) {
- View last = getChildAt(getChildCount() - 1);
- FrameLayout frame = new FrameLayout(getContext());
- frame.setLayoutParams(last.getLayoutParams());
-
- removeView(last);
- frame.addView(last, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
- TextView v = new TextView(getContext());
- v.setTextColor(Color.WHITE);
- v.setBackgroundColor(0x4d000000);
- v.setText(getResources().getString(R.string.slice_more_content, numExtra));
- v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
- v.setGravity(Gravity.CENTER);
- frame.addView(v, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
- addView(frame);
- }
-
- private boolean isFull() {
- return getChildCount() >= (mIsAllImages ? MAX_IMAGES : MAX_ALL);
- }
-
- /**
- * Returns true if this item is just an image.
- */
- private boolean addItem(SliceItem item) {
- if (item.getType() == SliceItem.TYPE_IMAGE) {
- ImageView v = new ImageView(getContext());
- v.setImageIcon(item.getIcon());
- v.setScaleType(ScaleType.CENTER_CROP);
- addView(v, new LayoutParams(0, MATCH_PARENT, 1));
- return true;
- } else {
- LinearLayout v = new LinearLayout(getContext());
- int s = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 12, getContext().getResources().getDisplayMetrics());
- v.setPadding(0, s, 0, 0);
- v.setOrientation(LinearLayout.VERTICAL);
- v.setGravity(Gravity.CENTER_HORIZONTAL);
- // TODO: Unify sporadic inflates that happen throughout the code.
- ArrayList items = new ArrayList<>();
- if (item.getType() == SliceItem.TYPE_SLICE) {
- items.addAll(item.getSlice().getItems());
- }
- items.forEach(i -> {
- Context context = getContext();
- switch (i.getType()) {
- case SliceItem.TYPE_TEXT:
- boolean title = false;
- if ((item.hasAnyHints(new String[] {
- Slice.HINT_LARGE, Slice.HINT_TITLE
- }))) {
- title = true;
- }
- TextView tv = (TextView) LayoutInflater.from(context).inflate(
- title ? R.layout.slice_title : R.layout.slice_secondary_text, null);
- tv.setText(i.getText());
- v.addView(tv);
- break;
- case SliceItem.TYPE_IMAGE:
- ImageView iv = new ImageView(context);
- iv.setImageIcon(i.getIcon());
- if (item.hasHint(Slice.HINT_LARGE)) {
- iv.setLayoutParams(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- } else {
- int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 48, context.getResources().getDisplayMetrics());
- iv.setLayoutParams(new LayoutParams(size, size));
- }
- v.addView(iv);
- break;
- case SliceItem.TYPE_REMOTE_VIEW:
- v.addView(i.getRemoteView().apply(context, v));
- break;
- case SliceItem.TYPE_COLOR:
- // TODO: Support color to tint stuff here.
- break;
- }
- });
- addView(v, new LayoutParams(0, WRAP_CONTENT, 1));
- return false;
- }
- }
-}
diff --git a/android/app/slice/views/LargeSliceAdapter.java b/android/app/slice/views/LargeSliceAdapter.java
deleted file mode 100644
index 6794ff98..00000000
--- a/android/app/slice/views/LargeSliceAdapter.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceViewHolder;
-import android.content.Context;
-import android.util.ArrayMap;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.widget.FrameLayout;
-
-import com.android.internal.R;
-import com.android.internal.widget.RecyclerView;
-import com.android.internal.widget.RecyclerView.ViewHolder;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * @hide
- */
-public class LargeSliceAdapter extends RecyclerView.Adapter {
-
- public static final int TYPE_DEFAULT = 1;
- public static final int TYPE_HEADER = 2;
- public static final int TYPE_GRID = 3;
- public static final int TYPE_MESSAGE = 4;
- public static final int TYPE_MESSAGE_LOCAL = 5;
- public static final int TYPE_REMOTE_VIEWS = 6;
-
- private final IdGenerator mIdGen = new IdGenerator();
- private final Context mContext;
- private List mSlices = new ArrayList<>();
- private SliceItem mColor;
-
- public LargeSliceAdapter(Context context) {
- mContext = context;
- setHasStableIds(true);
- }
-
- /**
- * Set the {@link SliceItem}'s to be displayed in the adapter and the accent color.
- */
- public void setSliceItems(List slices, SliceItem color) {
- mColor = color;
- mIdGen.resetUsage();
- mSlices = slices.stream().map(s -> new SliceWrapper(s, mIdGen))
- .collect(Collectors.toList());
- notifyDataSetChanged();
- }
-
- @Override
- public SliceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v = inflateforType(viewType);
- v.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
- return new SliceViewHolder(v);
- }
-
- @Override
- public int getItemViewType(int position) {
- return mSlices.get(position).mType;
- }
-
- @Override
- public long getItemId(int position) {
- return mSlices.get(position).mId;
- }
-
- @Override
- public int getItemCount() {
- return mSlices.size();
- }
-
- @Override
- public void onBindViewHolder(SliceViewHolder holder, int position) {
- SliceWrapper slice = mSlices.get(position);
- if (holder.mSliceView != null) {
- holder.mSliceView.setColor(mColor);
- holder.mSliceView.setSliceItem(slice.mItem);
- } else if (slice.mType == TYPE_REMOTE_VIEWS) {
- FrameLayout frame = (FrameLayout) holder.itemView;
- frame.removeAllViews();
- frame.addView(slice.mItem.getRemoteView().apply(mContext, frame));
- }
- }
-
- private View inflateforType(int viewType) {
- switch (viewType) {
- case TYPE_REMOTE_VIEWS:
- return new FrameLayout(mContext);
- case TYPE_GRID:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_grid, null);
- case TYPE_MESSAGE:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_message, null);
- case TYPE_MESSAGE_LOCAL:
- return LayoutInflater.from(mContext).inflate(R.layout.slice_message_local, null);
- }
- return new SmallTemplateView(mContext);
- }
-
- protected static class SliceWrapper {
- private final SliceItem mItem;
- private final int mType;
- private final long mId;
-
- public SliceWrapper(SliceItem item, IdGenerator idGen) {
- mItem = item;
- mType = getType(item);
- mId = idGen.getId(item);
- }
-
- public static int getType(SliceItem item) {
- if (item.getType() == SliceItem.TYPE_REMOTE_VIEW) {
- return TYPE_REMOTE_VIEWS;
- }
- if (item.hasHint(Slice.HINT_MESSAGE)) {
- // TODO: Better way to determine me or not? Something more like Messaging style.
- if (SliceQuery.find(item, -1, Slice.HINT_SOURCE, null) != null) {
- return TYPE_MESSAGE;
- } else {
- return TYPE_MESSAGE_LOCAL;
- }
- }
- if (item.hasHint(Slice.HINT_HORIZONTAL)) {
- return TYPE_GRID;
- }
- return TYPE_DEFAULT;
- }
- }
-
- /**
- * A {@link ViewHolder} for presenting slices in {@link LargeSliceAdapter}.
- */
- public static class SliceViewHolder extends ViewHolder {
- public final SliceListView mSliceView;
-
- public SliceViewHolder(View itemView) {
- super(itemView);
- mSliceView = itemView instanceof SliceListView ? (SliceListView) itemView : null;
- }
- }
-
- /**
- * View slices being displayed in {@link LargeSliceAdapter}.
- */
- public interface SliceListView {
- /**
- * Set the slice item for this view.
- */
- void setSliceItem(SliceItem slice);
-
- /**
- * Set the color for the items in this view.
- */
- default void setColor(SliceItem color) {
-
- }
- }
-
- private static class IdGenerator {
- private long mNextLong = 0;
- private final ArrayMap mCurrentIds = new ArrayMap<>();
- private final ArrayMap mUsedIds = new ArrayMap<>();
-
- public long getId(SliceItem item) {
- String str = genString(item);
- if (!mCurrentIds.containsKey(str)) {
- mCurrentIds.put(str, mNextLong++);
- }
- long id = mCurrentIds.get(str);
- int index = mUsedIds.getOrDefault(str, 0);
- mUsedIds.put(str, index + 1);
- return id + index * 10000;
- }
-
- private String genString(SliceItem item) {
- StringBuilder builder = new StringBuilder();
- SliceQuery.stream(item).forEach(i -> {
- builder.append(i.getType());
- i.removeHint(Slice.HINT_SELECTED);
- builder.append(i.getHints());
- switch (i.getType()) {
- case SliceItem.TYPE_REMOTE_VIEW:
- builder.append(i.getRemoteView());
- break;
- case SliceItem.TYPE_IMAGE:
- builder.append(i.getIcon());
- break;
- case SliceItem.TYPE_TEXT:
- builder.append(i.getText());
- break;
- case SliceItem.TYPE_COLOR:
- builder.append(i.getColor());
- break;
- }
- });
- return builder.toString();
- }
-
- public void resetUsage() {
- mUsedIds.clear();
- }
- }
-}
diff --git a/android/app/slice/views/LargeTemplateView.java b/android/app/slice/views/LargeTemplateView.java
deleted file mode 100644
index 9e225162..00000000
--- a/android/app/slice/views/LargeTemplateView.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.views.SliceView.SliceModeView;
-import android.content.Context;
-import android.util.TypedValue;
-
-import com.android.internal.widget.LinearLayoutManager;
-import com.android.internal.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @hide
- */
-public class LargeTemplateView extends SliceModeView {
- private final LargeSliceAdapter mAdapter;
- private final RecyclerView mRecyclerView;
- private final int mDefaultHeight;
- private final int mMaxHeight;
- private Slice mSlice;
-
- public LargeTemplateView(Context context) {
- super(context);
-
- mRecyclerView = new RecyclerView(getContext());
- mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
- mAdapter = new LargeSliceAdapter(context);
- mRecyclerView.setAdapter(mAdapter);
- addView(mRecyclerView);
- int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300,
- getResources().getDisplayMetrics());
- setLayoutParams(new LayoutParams(width, WRAP_CONTENT));
- mDefaultHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
- getResources().getDisplayMetrics());
- mMaxHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200,
- getResources().getDisplayMetrics());
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_LARGE;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- mRecyclerView.getLayoutParams().height = WRAP_CONTENT;
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mRecyclerView.getMeasuredHeight() > mMaxHeight
- || mSlice.hasHint(Slice.HINT_PARTIAL)) {
- mRecyclerView.getLayoutParams().height = mDefaultHeight;
- } else {
- mRecyclerView.getLayoutParams().height = mRecyclerView.getMeasuredHeight();
- }
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public void setSlice(Slice slice) {
- SliceItem color = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- mSlice = slice;
- List items = new ArrayList<>();
- boolean[] hasHeader = new boolean[1];
- if (slice.hasHint(Slice.HINT_LIST)) {
- addList(slice, items);
- } else {
- slice.getItems().forEach(item -> {
- if (item.hasHint(Slice.HINT_ACTIONS)) {
- return;
- } else if (item.getType() == SliceItem.TYPE_COLOR) {
- return;
- } else if (item.getType() == SliceItem.TYPE_SLICE
- && item.hasHint(Slice.HINT_LIST)) {
- addList(item.getSlice(), items);
- } else if (item.hasHint(Slice.HINT_LIST_ITEM)) {
- items.add(item);
- } else if (!hasHeader[0]) {
- hasHeader[0] = true;
- items.add(0, item);
- } else {
- item.addHint(Slice.HINT_LIST_ITEM);
- items.add(item);
- }
- });
- }
- mAdapter.setSliceItems(items, color);
- }
-
- private void addList(Slice slice, List items) {
- List sliceItems = slice.getItems();
- sliceItems.forEach(i -> i.addHint(Slice.HINT_LIST_ITEM));
- items.addAll(sliceItems);
- }
-}
diff --git a/android/app/slice/views/MessageView.java b/android/app/slice/views/MessageView.java
deleted file mode 100644
index 77252bf2..00000000
--- a/android/app/slice/views/MessageView.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.text.SpannableStringBuilder;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-/**
- * @hide
- */
-public class MessageView extends LinearLayout implements SliceListView {
-
- private TextView mDetails;
- private ImageView mIcon;
-
- public MessageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mDetails = findViewById(android.R.id.summary);
- mIcon = findViewById(android.R.id.icon);
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- SliceItem source = SliceQuery.find(slice, SliceItem.TYPE_IMAGE, Slice.HINT_SOURCE, null);
- if (source != null) {
- final int iconSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- 24, getContext().getResources().getDisplayMetrics());
- // TODO try and turn this into a drawable
- Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
- Canvas iconCanvas = new Canvas(iconBm);
- Drawable d = source.getIcon().loadDrawable(getContext());
- d.setBounds(0, 0, iconSize, iconSize);
- d.draw(iconCanvas);
- mIcon.setImageBitmap(SliceViewUtil.getCircularBitmap(iconBm));
- }
- SpannableStringBuilder builder = new SpannableStringBuilder();
- SliceQuery.findAll(slice, SliceItem.TYPE_TEXT).forEach(text -> {
- if (builder.length() != 0) {
- builder.append('\n');
- }
- builder.append(text.getText());
- });
- mDetails.setText(builder.toString());
- }
-
-}
diff --git a/android/app/slice/views/RemoteInputView.java b/android/app/slice/views/RemoteInputView.java
deleted file mode 100644
index e53cb1ea..00000000
--- a/android/app/slice/views/RemoteInputView.java
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import android.animation.Animator;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutManager;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.internal.R;
-
-/**
- * Host for the remote input.
- *
- * @hide
- */
-// TODO this should be unified with SystemUI RemoteInputView (b/67527720)
-public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
-
- private static final String TAG = "RemoteInput";
-
- /**
- * A marker object that let's us easily find views of this class.
- */
- public static final Object VIEW_TAG = new Object();
-
- private RemoteEditText mEditText;
- private ImageButton mSendButton;
- private ProgressBar mProgressBar;
- private PendingIntent mPendingIntent;
- private RemoteInput[] mRemoteInputs;
- private RemoteInput mRemoteInput;
-
- private int mRevealCx;
- private int mRevealCy;
- private int mRevealR;
- private boolean mResetting;
-
- public RemoteInputView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mProgressBar = findViewById(R.id.remote_input_progress);
- mSendButton = findViewById(R.id.remote_input_send);
- mSendButton.setOnClickListener(this);
-
- mEditText = (RemoteEditText) getChildAt(0);
- mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- final boolean isSoftImeEvent = event == null
- && (actionId == EditorInfo.IME_ACTION_DONE
- || actionId == EditorInfo.IME_ACTION_NEXT
- || actionId == EditorInfo.IME_ACTION_SEND);
- final boolean isKeyboardEnterKey = event != null
- && KeyEvent.isConfirmKey(event.getKeyCode())
- && event.getAction() == KeyEvent.ACTION_DOWN;
-
- if (isSoftImeEvent || isKeyboardEnterKey) {
- if (mEditText.length() > 0) {
- sendRemoteInput();
- }
- // Consume action to prevent IME from closing.
- return true;
- }
- return false;
- }
- });
- mEditText.addTextChangedListener(this);
- mEditText.setInnerFocusable(false);
- mEditText.mRemoteInputView = this;
- }
-
- private void sendRemoteInput() {
- Bundle results = new Bundle();
- results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
- Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
- results);
-
- mEditText.setEnabled(false);
- mSendButton.setVisibility(INVISIBLE);
- mProgressBar.setVisibility(VISIBLE);
- mEditText.mShowImeOnInputConnection = false;
-
- // Tell ShortcutManager that this package has been "activated". ShortcutManager
- // will reset the throttling for this package.
- // Strictly speaking, the intent receiver may be different from the intent creator,
- // but that's an edge case, and also because we can't always know which package will receive
- // an intent, so we just reset for the creator.
- getContext().getSystemService(ShortcutManager.class).onApplicationActive(
- mPendingIntent.getCreatorPackage(),
- getContext().getUserId());
-
- try {
- mPendingIntent.send(mContext, 0, fillInIntent);
- reset();
- } catch (PendingIntent.CanceledException e) {
- Log.i(TAG, "Unable to send remote input result", e);
- Toast.makeText(mContext, "Failure sending pending intent for inline reply :(",
- Toast.LENGTH_SHORT).show();
- reset();
- }
- }
-
- /**
- * Creates a remote input view.
- */
- public static RemoteInputView inflate(Context context, ViewGroup root) {
- RemoteInputView v = (RemoteInputView) LayoutInflater.from(context).inflate(
- R.layout.slice_remote_input, root, false);
- v.setTag(VIEW_TAG);
- return v;
- }
-
- @Override
- public void onClick(View v) {
- if (v == mSendButton) {
- sendRemoteInput();
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- super.onTouchEvent(event);
-
- // We never want for a touch to escape to an outer view or one we covered.
- return true;
- }
-
- private void onDefocus() {
- setVisibility(INVISIBLE);
- }
-
- /**
- * Set the pending intent for remote input.
- */
- public void setPendingIntent(PendingIntent pendingIntent) {
- mPendingIntent = pendingIntent;
- }
-
- /**
- * Set the remote inputs for this view.
- */
- public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
- mRemoteInputs = remoteInputs;
- mRemoteInput = remoteInput;
- mEditText.setHint(mRemoteInput.getLabel());
- }
-
- /**
- * Focuses the remote input view.
- */
- public void focusAnimated() {
- if (getVisibility() != VISIBLE) {
- Animator animator = ViewAnimationUtils.createCircularReveal(
- this, mRevealCx, mRevealCy, 0, mRevealR);
- animator.setDuration(200);
- animator.start();
- }
- focus();
- }
-
- private void focus() {
- setVisibility(VISIBLE);
- mEditText.setInnerFocusable(true);
- mEditText.mShowImeOnInputConnection = true;
- mEditText.setSelection(mEditText.getText().length());
- mEditText.requestFocus();
- updateSendButton();
- }
-
- private void reset() {
- mResetting = true;
-
- mEditText.getText().clear();
- mEditText.setEnabled(true);
- mSendButton.setVisibility(VISIBLE);
- mProgressBar.setVisibility(INVISIBLE);
- updateSendButton();
- onDefocus();
-
- mResetting = false;
- }
-
- @Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (mResetting && child == mEditText) {
- // Suppress text events if it happens during resetting. Ideally this would be
- // suppressed by the text view not being shown, but that doesn't work here because it
- // needs to stay visible for the animation.
- return false;
- }
- return super.onRequestSendAccessibilityEvent(child, event);
- }
-
- private void updateSendButton() {
- mSendButton.setEnabled(mEditText.getText().length() != 0);
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- }
-
- @Override
- public void afterTextChanged(Editable s) {
- updateSendButton();
- }
-
- /**
- * Tries to find an action that matches the current pending intent of this view and updates its
- * state to that of the found action
- *
- * @return true if a matching action was found, false otherwise
- */
- public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
- if (mPendingIntent == null || actions == null) {
- return false;
- }
- Intent current = mPendingIntent.getIntent();
- if (current == null) {
- return false;
- }
-
- for (Notification.Action a : actions) {
- RemoteInput[] inputs = a.getRemoteInputs();
- if (a.actionIntent == null || inputs == null) {
- continue;
- }
- Intent candidate = a.actionIntent.getIntent();
- if (!current.filterEquals(candidate)) {
- continue;
- }
-
- RemoteInput input = null;
- for (RemoteInput i : inputs) {
- if (i.getAllowFreeFormInput()) {
- input = i;
- }
- }
- if (input == null) {
- continue;
- }
- setPendingIntent(a.actionIntent);
- setRemoteInput(inputs, input);
- return true;
- }
- return false;
- }
-
- /**
- * @hide
- */
- public void setRevealParameters(int cx, int cy, int r) {
- mRevealCx = cx;
- mRevealCy = cy;
- mRevealR = r;
- }
-
- @Override
- public void dispatchStartTemporaryDetach() {
- super.dispatchStartTemporaryDetach();
- // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
- // won't lose IME focus.
- detachViewFromParent(mEditText);
- }
-
- @Override
- public void dispatchFinishTemporaryDetach() {
- if (isAttachedToWindow()) {
- attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
- } else {
- removeDetachedView(mEditText, false /* animate */);
- }
- super.dispatchFinishTemporaryDetach();
- }
-
- /**
- * An EditText that changes appearance based on whether it's focusable and becomes un-focusable
- * whenever the user navigates away from it or it becomes invisible.
- */
- public static class RemoteEditText extends EditText {
-
- private final Drawable mBackground;
- private RemoteInputView mRemoteInputView;
- boolean mShowImeOnInputConnection;
-
- public RemoteEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- mBackground = getBackground();
- }
-
- private void defocusIfNeeded(boolean animate) {
- if (mRemoteInputView != null || isTemporarilyDetached()) {
- if (isTemporarilyDetached()) {
- // We might get reattached but then the other one of HUN / expanded might steal
- // our focus, so we'll need to save our text here.
- }
- return;
- }
- if (isFocusable() && isEnabled()) {
- setInnerFocusable(false);
- if (mRemoteInputView != null) {
- mRemoteInputView.onDefocus();
- }
- mShowImeOnInputConnection = false;
- }
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
-
- if (!isShown()) {
- defocusIfNeeded(false /* animate */);
- }
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- if (!focused) {
- defocusIfNeeded(true /* animate */);
- }
- }
-
- @Override
- public void getFocusedRect(Rect r) {
- super.getFocusedRect(r);
- r.top = mScrollY;
- r.bottom = mScrollY + (mBottom - mTop);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // Eat the DOWN event here to prevent any default behavior.
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- defocusIfNeeded(true /* animate */);
- return true;
- }
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
-
- if (mShowImeOnInputConnection && inputConnection != null) {
- final InputMethodManager imm = InputMethodManager.getInstance();
- if (imm != null) {
- // onCreateInputConnection is called by InputMethodManager in the middle of
- // setting up the connection to the IME; wait with requesting the IME until that
- // work has completed.
- post(new Runnable() {
- @Override
- public void run() {
- imm.viewClicked(RemoteEditText.this);
- imm.showSoftInput(RemoteEditText.this, 0);
- }
- });
- }
- }
-
- return inputConnection;
- }
-
- @Override
- public void onCommitCompletion(CompletionInfo text) {
- clearComposingText();
- setText(text.getText());
- setSelection(getText().length());
- }
-
- void setInnerFocusable(boolean focusable) {
- setFocusableInTouchMode(focusable);
- setFocusable(focusable);
- setCursorVisible(focusable);
-
- if (focusable) {
- requestFocus();
- setBackground(mBackground);
- } else {
- setBackground(null);
- }
-
- }
- }
-}
diff --git a/android/app/slice/views/ShortcutView.java b/android/app/slice/views/ShortcutView.java
deleted file mode 100644
index b6790c7d..00000000
--- a/android/app/slice/views/ShortcutView.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.views.SliceView.SliceModeView;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Color;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.OvalShape;
-import android.net.Uri;
-import android.view.ViewGroup;
-
-import com.android.internal.R;
-
-/**
- * @hide
- */
-public class ShortcutView extends SliceModeView {
-
- private static final String TAG = "ShortcutView";
-
- private PendingIntent mAction;
- private Uri mUri;
- private int mLargeIconSize;
- private int mSmallIconSize;
-
- public ShortcutView(Context context) {
- super(context);
- mLargeIconSize = getContext().getResources()
- .getDimensionPixelSize(R.dimen.slice_shortcut_size);
- mSmallIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
- setLayoutParams(new ViewGroup.LayoutParams(mLargeIconSize, mLargeIconSize));
- }
-
- @Override
- public void setSlice(Slice slice) {
- removeAllViews();
- SliceItem sliceItem = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
- SliceItem iconItem = slice.getPrimaryIcon();
- SliceItem textItem = sliceItem != null
- ? SliceQuery.find(sliceItem, SliceItem.TYPE_TEXT)
- : SliceQuery.find(slice, SliceItem.TYPE_TEXT);
- SliceItem colorItem = sliceItem != null
- ? SliceQuery.find(sliceItem, SliceItem.TYPE_COLOR)
- : SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- if (colorItem == null) {
- colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- }
- // TODO: pick better default colour
- final int color = colorItem != null ? colorItem.getColor() : Color.GRAY;
- ShapeDrawable circle = new ShapeDrawable(new OvalShape());
- circle.setTint(color);
- setBackground(circle);
- if (iconItem != null) {
- final boolean isLarge = iconItem.hasHint(Slice.HINT_LARGE);
- final int iconSize = isLarge ? mLargeIconSize : mSmallIconSize;
- SliceViewUtil.createCircledIcon(getContext(), color, iconSize, iconItem.getIcon(),
- isLarge, this /* parent */);
- mAction = sliceItem != null ? sliceItem.getAction()
- : null;
- mUri = slice.getUri();
- setClickable(true);
- } else {
- setClickable(false);
- }
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_SHORTCUT;
- }
-
- @Override
- public boolean performClick() {
- if (!callOnClick()) {
- try {
- if (mAction != null) {
- mAction.send();
- } else {
- Intent intent = new Intent(Intent.ACTION_VIEW).setData(mUri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- getContext().startActivity(intent);
- }
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }
- return true;
- }
-}
diff --git a/android/app/slice/views/SliceView.java b/android/app/slice/views/SliceView.java
deleted file mode 100644
index 32484fca..00000000
--- a/android/app/slice/views/SliceView.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import android.annotation.StringDef;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import java.util.List;
-
-/**
- * A view that can display a {@link Slice} in different {@link SliceMode}'s.
- *
- * @hide
- */
-public class SliceView extends LinearLayout {
-
- private static final String TAG = "SliceView";
-
- /**
- * @hide
- */
- public abstract static class SliceModeView extends FrameLayout {
-
- public SliceModeView(Context context) {
- super(context);
- }
-
- /**
- * @return the {@link SliceMode} of the slice being presented.
- */
- public abstract String getMode();
-
- /**
- * @param slice the slice to show in this view.
- */
- public abstract void setSlice(Slice slice);
- }
-
- /**
- * @hide
- */
- @StringDef({
- MODE_SMALL, MODE_LARGE, MODE_SHORTCUT
- })
- public @interface SliceMode {}
-
- /**
- * Mode indicating this slice should be presented in small template format.
- */
- public static final String MODE_SMALL = "SLICE_SMALL";
- /**
- * Mode indicating this slice should be presented in large template format.
- */
- public static final String MODE_LARGE = "SLICE_LARGE";
- /**
- * Mode indicating this slice should be presented as an icon.
- */
- public static final String MODE_SHORTCUT = "SLICE_ICON";
-
- /**
- * Will select the type of slice binding based on size of the View. TODO: Put in some info about
- * that selection.
- */
- private static final String MODE_AUTO = "auto";
-
- private String mMode = MODE_AUTO;
- private SliceModeView mCurrentView;
- private final ActionRow mActions;
- private Slice mCurrentSlice;
- private boolean mShowActions = true;
-
- /**
- * Simple constructor to create a slice view from code.
- *
- * @param context The context the view is running in.
- */
- public SliceView(Context context) {
- super(context);
- setOrientation(LinearLayout.VERTICAL);
- mActions = new ActionRow(mContext, true);
- mActions.setBackground(new ColorDrawable(0xffeeeeee));
- mCurrentView = new LargeTemplateView(mContext);
- addView(mCurrentView);
- addView(mActions);
- }
-
- /**
- * @hide
- */
- public void bindSlice(Intent intent) {
- // TODO
- }
-
- /**
- * Binds this view to the {@link Slice} associated with the provided {@link Uri}.
- */
- public void bindSlice(Uri sliceUri) {
- validate(sliceUri);
- Slice s = Slice.bindSlice(mContext.getContentResolver(), sliceUri);
- bindSlice(s);
- }
-
- /**
- * Binds this view to the provided {@link Slice}.
- */
- public void bindSlice(Slice slice) {
- mCurrentSlice = slice;
- if (mCurrentSlice != null) {
- reinflate();
- }
- }
-
- /**
- * Call to clean up the view.
- */
- public void unbindSlice() {
- mCurrentSlice = null;
- }
-
- /**
- * Set the {@link SliceMode} this view should present in.
- */
- public void setMode(@SliceMode String mode) {
- setMode(mode, false /* animate */);
- }
-
- /**
- * @hide
- */
- public void setMode(@SliceMode String mode, boolean animate) {
- if (animate) {
- Log.e(TAG, "Animation not supported yet");
- }
- mMode = mode;
- reinflate();
- }
-
- /**
- * @return the {@link SliceMode} this view is presenting in.
- */
- public @SliceMode String getMode() {
- if (mMode.equals(MODE_AUTO)) {
- return MODE_LARGE;
- }
- return mMode;
- }
-
- /**
- * @hide
- *
- * Whether this view should show a row of actions with it.
- */
- public void setShowActionRow(boolean show) {
- mShowActions = show;
- reinflate();
- }
-
- private SliceModeView createView(String mode) {
- switch (mode) {
- case MODE_SHORTCUT:
- return new ShortcutView(getContext());
- case MODE_SMALL:
- return new SmallTemplateView(getContext());
- }
- return new LargeTemplateView(getContext());
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- unbindSlice();
- }
-
- private void reinflate() {
- if (mCurrentSlice == null) {
- return;
- }
- // TODO: Smarter mapping here from one state to the next.
- SliceItem color = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_COLOR);
- List items = mCurrentSlice.getItems();
- SliceItem actionRow = SliceQuery.find(mCurrentSlice, SliceItem.TYPE_SLICE,
- Slice.HINT_ACTIONS,
- Slice.HINT_ALT);
- String mode = getMode();
- if (!mode.equals(mCurrentView.getMode())) {
- removeAllViews();
- mCurrentView = createView(mode);
- addView(mCurrentView);
- addView(mActions);
- }
- if (items.size() > 1 || (items.size() != 0 && items.get(0) != actionRow)) {
- mCurrentView.setVisibility(View.VISIBLE);
- mCurrentView.setSlice(mCurrentSlice);
- } else {
- mCurrentView.setVisibility(View.GONE);
- }
-
- boolean showActions = mShowActions && actionRow != null
- && !mode.equals(MODE_SHORTCUT);
- if (showActions) {
- mActions.setActions(actionRow, color);
- mActions.setVisibility(View.VISIBLE);
- } else {
- mActions.setVisibility(View.GONE);
- }
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- // TODO -- may need to rethink for AGSA
- if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
- requestDisallowInterceptTouchEvent(true);
- }
- return super.onInterceptTouchEvent(ev);
- }
-
- private static void validate(Uri sliceUri) {
- if (!ContentResolver.SCHEME_CONTENT.equals(sliceUri.getScheme())) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- if (sliceUri.getPathSegments().size() == 0) {
- throw new RuntimeException("Invalid uri " + sliceUri);
- }
- }
-}
diff --git a/android/app/slice/views/SliceViewUtil.java b/android/app/slice/views/SliceViewUtil.java
deleted file mode 100644
index 19e8e7c9..00000000
--- a/android/app/slice/views/SliceViewUtil.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import android.annotation.ColorInt;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.view.Gravity;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-
-/**
- * A bunch of utilities for slice UI.
- *
- * @hide
- */
-public class SliceViewUtil {
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorAccent(Context context) {
- return getColorAttr(context, android.R.attr.colorAccent);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorError(Context context) {
- return getColorAttr(context, android.R.attr.colorError);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getDefaultColor(Context context, int resId) {
- final ColorStateList list = context.getResources().getColorStateList(resId,
- context.getTheme());
-
- return list.getDefaultColor();
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getDisabled(Context context, int inputColor) {
- return applyAlphaAttr(context, android.R.attr.disabledAlpha, inputColor);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int applyAlphaAttr(Context context, int attr, int inputColor) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- float alpha = ta.getFloat(0, 0);
- ta.recycle();
- return applyAlpha(alpha, inputColor);
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int applyAlpha(float alpha, int inputColor) {
- alpha *= Color.alpha(inputColor);
- return Color.argb((int) (alpha), Color.red(inputColor), Color.green(inputColor),
- Color.blue(inputColor));
- }
-
- /**
- * @hide
- */
- @ColorInt
- public static int getColorAttr(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- @ColorInt
- int colorAccent = ta.getColor(0, 0);
- ta.recycle();
- return colorAccent;
- }
-
- /**
- * @hide
- */
- public static int getThemeAttr(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- int theme = ta.getResourceId(0, 0);
- ta.recycle();
- return theme;
- }
-
- /**
- * @hide
- */
- public static Drawable getDrawable(Context context, int attr) {
- TypedArray ta = context.obtainStyledAttributes(new int[] {
- attr
- });
- Drawable drawable = ta.getDrawable(0);
- ta.recycle();
- return drawable;
- }
-
- /**
- * @hide
- */
- public static void createCircledIcon(Context context, int color, int iconSize, Icon icon,
- boolean isLarge, ViewGroup parent) {
- ImageView v = new ImageView(context);
- v.setImageIcon(icon);
- parent.addView(v);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
- if (isLarge) {
- // XXX better way to convert from icon -> bitmap or crop an icon (?)
- Bitmap iconBm = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
- Canvas iconCanvas = new Canvas(iconBm);
- v.layout(0, 0, iconSize, iconSize);
- v.draw(iconCanvas);
- v.setImageBitmap(getCircularBitmap(iconBm));
- } else {
- v.setColorFilter(Color.WHITE);
- }
- lp.width = iconSize;
- lp.height = iconSize;
- lp.gravity = Gravity.CENTER;
- }
-
- /**
- * @hide
- */
- public static Bitmap getCircularBitmap(Bitmap bitmap) {
- Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
- bitmap.getHeight(), Config.ARGB_8888);
- Canvas canvas = new Canvas(output);
- final Paint paint = new Paint();
- final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
- paint.setAntiAlias(true);
- canvas.drawARGB(0, 0, 0, 0);
- canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2,
- bitmap.getWidth() / 2, paint);
- paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
- canvas.drawBitmap(bitmap, rect, rect, paint);
- return output;
- }
-}
diff --git a/android/app/slice/views/SmallTemplateView.java b/android/app/slice/views/SmallTemplateView.java
deleted file mode 100644
index 42b2d213..00000000
--- a/android/app/slice/views/SmallTemplateView.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 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 android.app.slice.views;
-
-import android.app.PendingIntent.CanceledException;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
-import android.app.slice.SliceQuery;
-import android.app.slice.views.LargeSliceAdapter.SliceListView;
-import android.app.slice.views.SliceView.SliceModeView;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.R;
-
-import java.text.Format;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Small template is also used to construct list items for use with {@link LargeTemplateView}.
- *
- * @hide
- */
-public class SmallTemplateView extends SliceModeView implements SliceListView {
-
- private static final String TAG = "SmallTemplateView";
-
- private int mIconSize;
- private int mPadding;
-
- private LinearLayout mStartContainer;
- private TextView mTitleText;
- private TextView mSecondaryText;
- private LinearLayout mEndContainer;
-
- public SmallTemplateView(Context context) {
- super(context);
- inflate(context, R.layout.slice_small_template, this);
- mIconSize = getContext().getResources().getDimensionPixelSize(R.dimen.slice_icon_size);
- mPadding = getContext().getResources().getDimensionPixelSize(R.dimen.slice_padding);
-
- mStartContainer = (LinearLayout) findViewById(android.R.id.icon_frame);
- mTitleText = (TextView) findViewById(android.R.id.title);
- mSecondaryText = (TextView) findViewById(android.R.id.summary);
- mEndContainer = (LinearLayout) findViewById(android.R.id.widget_frame);
- }
-
- @Override
- public String getMode() {
- return SliceView.MODE_SMALL;
- }
-
- @Override
- public void setSliceItem(SliceItem slice) {
- resetViews();
- SliceItem colorItem = SliceQuery.find(slice, SliceItem.TYPE_COLOR);
- int color = colorItem != null ? colorItem.getColor() : -1;
-
- // Look for any title elements
- List titleItems = SliceQuery.findAll(slice, -1, Slice.HINT_TITLE,
- null);
- boolean hasTitleText = false;
- boolean hasTitleItem = false;
- for (int i = 0; i < titleItems.size(); i++) {
- SliceItem item = titleItems.get(i);
- if (!hasTitleItem) {
- // icon, action icon, or timestamp
- if (item.getType() == SliceItem.TYPE_ACTION) {
- hasTitleItem = addIcon(item, color, mStartContainer);
- } else if (item.getType() == SliceItem.TYPE_IMAGE) {
- addIcon(item, color, mStartContainer);
- hasTitleItem = true;
- } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
- TextView tv = new TextView(getContext());
- tv.setText(convertTimeToString(item.getTimestamp()));
- hasTitleItem = true;
- }
- }
- if (!hasTitleText && item.getType() == SliceItem.TYPE_TEXT) {
- mTitleText.setText(item.getText());
- hasTitleText = true;
- }
- if (hasTitleText && hasTitleItem) {
- break;
- }
- }
- mTitleText.setVisibility(hasTitleText ? View.VISIBLE : View.GONE);
- mStartContainer.setVisibility(hasTitleItem ? View.VISIBLE : View.GONE);
-
- if (slice.getType() != SliceItem.TYPE_SLICE) {
- return;
- }
-
- // Deal with remaining items
- int itemCount = 0;
- boolean hasSummary = false;
- ArrayList sliceItems = new ArrayList(
- slice.getSlice().getItems());
- for (int i = 0; i < sliceItems.size(); i++) {
- SliceItem item = sliceItems.get(i);
- if (!hasSummary && item.getType() == SliceItem.TYPE_TEXT
- && !item.hasHint(Slice.HINT_TITLE)) {
- // TODO -- Should combine all text items?
- mSecondaryText.setText(item.getText());
- hasSummary = true;
- }
- if (itemCount <= 3) {
- if (item.getType() == SliceItem.TYPE_ACTION) {
- if (addIcon(item, color, mEndContainer)) {
- itemCount++;
- }
- } else if (item.getType() == SliceItem.TYPE_IMAGE) {
- addIcon(item, color, mEndContainer);
- itemCount++;
- } else if (item.getType() == SliceItem.TYPE_TIMESTAMP) {
- TextView tv = new TextView(getContext());
- tv.setText(convertTimeToString(item.getTimestamp()));
- mEndContainer.addView(tv);
- itemCount++;
- } else if (item.getType() == SliceItem.TYPE_SLICE) {
- List subItems = item.getSlice().getItems();
- for (int j = 0; j < subItems.size(); j++) {
- sliceItems.add(subItems.get(j));
- }
- }
- }
- }
- }
-
- @Override
- public void setSlice(Slice slice) {
- setSliceItem(new SliceItem(slice, SliceItem.TYPE_SLICE,
- slice.getHints().toArray(new String[slice.getHints().size()])));
- }
-
- /**
- * @return Whether an icon was added.
- */
- private boolean addIcon(SliceItem sliceItem, int color, LinearLayout container) {
- SliceItem image = null;
- SliceItem action = null;
- if (sliceItem.getType() == SliceItem.TYPE_ACTION) {
- image = SliceQuery.find(sliceItem.getSlice(), SliceItem.TYPE_IMAGE);
- action = sliceItem;
- } else if (sliceItem.getType() == SliceItem.TYPE_IMAGE) {
- image = sliceItem;
- }
- if (image != null) {
- ImageView iv = new ImageView(getContext());
- iv.setImageIcon(image.getIcon());
- if (action != null) {
- final SliceItem sliceAction = action;
- iv.setOnClickListener(v -> AsyncTask.execute(
- () -> {
- try {
- sliceAction.getAction().send();
- } catch (CanceledException e) {
- e.printStackTrace();
- }
- }));
- iv.setBackground(SliceViewUtil.getDrawable(getContext(),
- android.R.attr.selectableItemBackground));
- }
- if (color != -1 && !sliceItem.hasHint(Slice.HINT_NO_TINT)) {
- iv.setColorFilter(color);
- }
- container.addView(iv);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) iv.getLayoutParams();
- lp.width = mIconSize;
- lp.height = mIconSize;
- lp.setMarginStart(mPadding);
- return true;
- }
- return false;
- }
-
- private String convertTimeToString(long time) {
- // TODO -- figure out what format(s) we support
- Date date = new Date(time);
- Format format = new SimpleDateFormat("MM dd yyyy HH:mm:ss");
- return format.format(date);
- }
-
- private void resetViews() {
- mStartContainer.removeAllViews();
- mEndContainer.removeAllViews();
- mTitleText.setText(null);
- mSecondaryText.setText(null);
- }
-}
diff --git a/android/app/usage/UsageStatsManager.java b/android/app/usage/UsageStatsManager.java
index fd579fce..051dccbd 100644
--- a/android/app/usage/UsageStatsManager.java
+++ b/android/app/usage/UsageStatsManager.java
@@ -48,10 +48,10 @@ import java.util.Map;
*
* A request for data in the middle of a time interval will include that interval.
*
- * NOTE: This API requires the permission android.permission.PACKAGE_USAGE_STATS.
- * However, declaring the permission implies intention to use the API and the user of the device
- * still needs to grant permission through the Settings application.
- * See {@link android.provider.Settings#ACTION_USAGE_ACCESS_SETTINGS}
+ * NOTE: This API requires the permission android.permission.PACKAGE_USAGE_STATS, which
+ * is a system-level permission and will not be granted to third-party apps. However, declaring
+ * the permission implies intention to use the API and the user of the device can grant permission
+ * through the Settings application.
*/
@SystemService(Context.USAGE_STATS_SERVICE)
public final class UsageStatsManager {
@@ -122,7 +122,7 @@ public final class UsageStatsManager {
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A list of {@link UsageStats}
+ * @return A list of {@link UsageStats} or null if none are available.
*
* @see #INTERVAL_DAILY
* @see #INTERVAL_WEEKLY
@@ -139,7 +139,7 @@ public final class UsageStatsManager {
return slice.getList();
}
} catch (RemoteException e) {
- // fallthrough and return the empty list.
+ // fallthrough and return null.
}
return Collections.emptyList();
}
@@ -152,7 +152,7 @@ public final class UsageStatsManager {
* @param intervalType The time interval by which the stats are aggregated.
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A list of {@link ConfigurationStats}
+ * @return A list of {@link ConfigurationStats} or null if none are available.
*/
public List queryConfigurations(int intervalType, long beginTime,
long endTime) {
@@ -185,7 +185,7 @@ public final class UsageStatsManager {
return iter;
}
} catch (RemoteException e) {
- // fallthrough and return empty result.
+ // fallthrough and return null
}
return sEmptyResults;
}
@@ -197,7 +197,8 @@ public final class UsageStatsManager {
*
* @param beginTime The inclusive beginning of the range of stats to include in the results.
* @param endTime The exclusive end of the range of stats to include in the results.
- * @return A {@link java.util.Map} keyed by package name
+ * @return A {@link java.util.Map} keyed by package name, or null if no stats are
+ * available.
*/
public Map queryAndAggregateUsageStats(long beginTime, long endTime) {
List stats = queryUsageStats(INTERVAL_BEST, beginTime, endTime);
diff --git a/android/arch/lifecycle/ActivityFullLifecycleTest.java b/android/arch/lifecycle/ActivityFullLifecycleTest.java
index 78dd0150..ee4e661a 100644
--- a/android/arch/lifecycle/ActivityFullLifecycleTest.java
+++ b/android/arch/lifecycle/ActivityFullLifecycleTest.java
@@ -16,43 +16,48 @@
package android.arch.lifecycle;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
-import static android.arch.lifecycle.TestUtils.flatMap;
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle.Event;
-import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
-import android.arch.lifecycle.testapp.CollectingSupportActivity;
+import android.arch.lifecycle.testapp.CollectingActivity;
import android.arch.lifecycle.testapp.FrameworkLifecycleRegistryActivity;
+import android.arch.lifecycle.testapp.FullLifecycleTestActivity;
+import android.arch.lifecycle.testapp.SupportLifecycleRegistryActivity;
import android.arch.lifecycle.testapp.TestEvent;
import android.support.test.filters.SmallTest;
import android.support.test.rule.ActivityTestRule;
-import android.support.v4.util.Pair;
+import android.util.Pair;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(Parameterized.class)
public class ActivityFullLifecycleTest {
@Rule
- public final ActivityTestRule extends CollectingLifecycleOwner> activityTestRule;
+ public ActivityTestRule activityTestRule =
+ new ActivityTestRule<>(FullLifecycleTestActivity.class);
@Parameterized.Parameters
public static Class[] params() {
- return new Class[]{CollectingSupportActivity.class,
+ return new Class[]{FullLifecycleTestActivity.class,
+ SupportLifecycleRegistryActivity.class,
FrameworkLifecycleRegistryActivity.class};
}
@@ -63,13 +68,28 @@ public class ActivityFullLifecycleTest {
@Test
- public void testFullLifecycle() throws Throwable {
- CollectingLifecycleOwner owner = activityTestRule.getActivity();
- TestUtils.waitTillResumed(owner, activityTestRule);
- activityTestRule.finishActivity();
+ public void testFullLifecycle() throws InterruptedException {
+ Activity activity = activityTestRule.getActivity();
+ List> results = ((CollectingActivity) activity)
+ .waitForCollectedEvents();
- TestUtils.waitTillDestroyed(owner, activityTestRule);
- List> results = owner.copyCollectedEvents();
- assertThat(results, is(flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY)));
+ Event[] expectedEvents =
+ new Event[]{ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
+
+ List> expected = new ArrayList<>();
+ boolean beforeResume = true;
+ for (Event i : expectedEvents) {
+ if (beforeResume) {
+ expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
+ expected.add(new Pair<>(LIFECYCLE_EVENT, i));
+ } else {
+ expected.add(new Pair<>(LIFECYCLE_EVENT, i));
+ expected.add(new Pair<>(ACTIVITY_CALLBACK, i));
+ }
+ if (i == ON_RESUME) {
+ beforeResume = false;
+ }
+ }
+ assertThat(results, is(expected));
}
}
diff --git a/android/arch/lifecycle/AndroidViewModel.java b/android/arch/lifecycle/AndroidViewModel.java
index 106b2ef0..2c7e1739 100644
--- a/android/arch/lifecycle/AndroidViewModel.java
+++ b/android/arch/lifecycle/AndroidViewModel.java
@@ -16,9 +16,7 @@
package android.arch.lifecycle;
-import android.annotation.SuppressLint;
import android.app.Application;
-import android.support.annotation.NonNull;
/**
* Application context aware {@link ViewModel}.
@@ -27,19 +25,16 @@ import android.support.annotation.NonNull;
*
*/
public class AndroidViewModel extends ViewModel {
- @SuppressLint("StaticFieldLeak")
private Application mApplication;
- public AndroidViewModel(@NonNull Application application) {
+ public AndroidViewModel(Application application) {
mApplication = application;
}
/**
* Return the application.
*/
- @NonNull
public T getApplication() {
- //noinspection unchecked
return (T) mApplication;
}
}
diff --git a/android/arch/lifecycle/ClassesInfoCache.java b/android/arch/lifecycle/ClassesInfoCache.java
index d88e2762..f077daed 100644
--- a/android/arch/lifecycle/ClassesInfoCache.java
+++ b/android/arch/lifecycle/ClassesInfoCache.java
@@ -46,7 +46,7 @@ class ClassesInfoCache {
return mHasLifecycleMethods.get(klass);
}
- Method[] methods = getDeclaredMethods(klass);
+ Method[] methods = klass.getDeclaredMethods();
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
if (annotation != null) {
@@ -64,18 +64,6 @@ class ClassesInfoCache {
return false;
}
- private Method[] getDeclaredMethods(Class klass) {
- try {
- return klass.getDeclaredMethods();
- } catch (NoClassDefFoundError e) {
- throw new IllegalArgumentException("The observer class has some methods that use "
- + "newer APIs which are not available in the current OS version. Lifecycles "
- + "cannot access even other methods so you should make sure that your "
- + "observer classes only access framework classes that are available "
- + "in your min API level OR use lifecycle:compiler annotation processor.", e);
- }
- }
-
CallbackInfo getInfo(Class klass) {
CallbackInfo existing = mCallbackMap.get(klass);
if (existing != null) {
@@ -118,7 +106,7 @@ class ClassesInfoCache {
}
}
- Method[] methods = declaredMethods != null ? declaredMethods : getDeclaredMethods(klass);
+ Method[] methods = declaredMethods != null ? declaredMethods : klass.getDeclaredMethods();
boolean hasLifecycleMethods = false;
for (Method method : methods) {
OnLifecycleEvent annotation = method.getAnnotation(OnLifecycleEvent.class);
diff --git a/android/arch/lifecycle/ComputableLiveData.java b/android/arch/lifecycle/ComputableLiveData.java
index 1ddcb1a9..f1352446 100644
--- a/android/arch/lifecycle/ComputableLiveData.java
+++ b/android/arch/lifecycle/ComputableLiveData.java
@@ -1,136 +1,9 @@
-/*
- * Copyright (C) 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.
- */
-
+//ComputableLiveData interface for tests
package android.arch.lifecycle;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.annotation.VisibleForTesting;
-import android.support.annotation.WorkerThread;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * A LiveData class that can be invalidated & computed on demand.
- *
- * This is an internal class for now, might be public if we see the necessity.
- *
- * @param The type of the live data
- * @hide internal
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+import android.arch.lifecycle.LiveData;
public abstract class ComputableLiveData {
-
- private final LiveData mLiveData;
-
- private AtomicBoolean mInvalid = new AtomicBoolean(true);
- private AtomicBoolean mComputing = new AtomicBoolean(false);
-
- /**
- * Creates a computable live data which is computed when there are active observers.
- *
- * It can also be invalidated via {@link #invalidate()} which will result in a call to
- * {@link #compute()} if there are active observers (or when they start observing)
- */
- @SuppressWarnings("WeakerAccess")
- public ComputableLiveData() {
- mLiveData = new LiveData() {
- @Override
- protected void onActive() {
- // TODO if we make this class public, we should accept an executor
- ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- };
- }
-
- /**
- * Returns the LiveData managed by this class.
- *
- * @return A LiveData that is controlled by ComputableLiveData.
- */
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public LiveData getLiveData() {
- return mLiveData;
- }
-
- @VisibleForTesting
- final Runnable mRefreshRunnable = new Runnable() {
- @WorkerThread
- @Override
- public void run() {
- boolean computed;
- do {
- computed = false;
- // compute can happen only in 1 thread but no reason to lock others.
- if (mComputing.compareAndSet(false, true)) {
- // as long as it is invalid, keep computing.
- try {
- T value = null;
- while (mInvalid.compareAndSet(true, false)) {
- computed = true;
- value = compute();
- }
- if (computed) {
- mLiveData.postValue(value);
- }
- } finally {
- // release compute lock
- mComputing.set(false);
- }
- }
- // check invalid after releasing compute lock to avoid the following scenario.
- // Thread A runs compute()
- // Thread A checks invalid, it is false
- // Main thread sets invalid to true
- // Thread B runs, fails to acquire compute lock and skips
- // Thread A releases compute lock
- // We've left invalid in set state. The check below recovers.
- } while (computed && mInvalid.get());
- }
- };
-
- // invalidation check always happens on the main thread
- @VisibleForTesting
- final Runnable mInvalidationRunnable = new Runnable() {
- @MainThread
- @Override
- public void run() {
- boolean isActive = mLiveData.hasActiveObservers();
- if (mInvalid.compareAndSet(false, true)) {
- if (isActive) {
- // TODO if we make this class public, we should accept an executor.
- ArchTaskExecutor.getInstance().executeOnDiskIO(mRefreshRunnable);
- }
- }
- }
- };
-
- /**
- * Invalidates the LiveData.
- *
- * When there are active observers, this will trigger a call to {@link #compute()}.
- */
- public void invalidate() {
- ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable);
- }
-
- @SuppressWarnings("WeakerAccess")
- @WorkerThread
- protected abstract T compute();
+ public ComputableLiveData(){}
+ abstract protected T compute();
+ public LiveData getLiveData() {return null;}
+ public void invalidate() {}
}
diff --git a/android/arch/lifecycle/DispatcherActivityCallbackTest.java b/android/arch/lifecycle/DispatcherActivityCallbackTest.java
new file mode 100644
index 00000000..86b25b60
--- /dev/null
+++ b/android/arch/lifecycle/DispatcherActivityCallbackTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 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 android.arch.lifecycle;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DispatcherActivityCallbackTest {
+ @Test
+ public void onCreateFrameworkActivity() {
+ LifecycleDispatcher.DispatcherActivityCallback callback =
+ new LifecycleDispatcher.DispatcherActivityCallback();
+ Activity activity = mock(Activity.class);
+ checkReportFragment(callback, activity);
+ }
+
+ @Test
+ public void onCreateFragmentActivity() {
+ LifecycleDispatcher.DispatcherActivityCallback callback =
+ new LifecycleDispatcher.DispatcherActivityCallback();
+ FragmentActivity activity = mock(FragmentActivity.class);
+ FragmentManager fragmentManager = mock(FragmentManager.class);
+ when(activity.getSupportFragmentManager()).thenReturn(fragmentManager);
+
+ checkReportFragment(callback, activity);
+
+ verify(activity).getSupportFragmentManager();
+ verify(fragmentManager).registerFragmentLifecycleCallbacks(
+ any(FragmentManager.FragmentLifecycleCallbacks.class), eq(true));
+ }
+
+ @SuppressLint("CommitTransaction")
+ private void checkReportFragment(LifecycleDispatcher.DispatcherActivityCallback callback,
+ Activity activity) {
+ android.app.FragmentManager fm = mock(android.app.FragmentManager.class);
+ FragmentTransaction transaction = mock(FragmentTransaction.class);
+ when(activity.getFragmentManager()).thenReturn(fm);
+ when(fm.beginTransaction()).thenReturn(transaction);
+ when(transaction.add(any(Fragment.class), anyString())).thenReturn(transaction);
+ callback.onActivityCreated(activity, mock(Bundle.class));
+ verify(activity).getFragmentManager();
+ verify(fm).beginTransaction();
+ verify(transaction).add(any(ReportFragment.class), anyString());
+ verify(transaction).commit();
+ }
+}
diff --git a/android/arch/lifecycle/Lifecycle.java b/android/arch/lifecycle/Lifecycle.java
index c0a2090c..02db5ff9 100644
--- a/android/arch/lifecycle/Lifecycle.java
+++ b/android/arch/lifecycle/Lifecycle.java
@@ -17,7 +17,6 @@
package android.arch.lifecycle;
import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
/**
* Defines an object that has an Android Lifecycle. {@link android.support.v4.app.Fragment Fragment}
@@ -84,7 +83,7 @@ public abstract class Lifecycle {
* @param observer The observer to notify.
*/
@MainThread
- public abstract void addObserver(@NonNull LifecycleObserver observer);
+ public abstract void addObserver(LifecycleObserver observer);
/**
* Removes the given observer from the observers list.
@@ -100,7 +99,7 @@ public abstract class Lifecycle {
* @param observer The observer to be removed.
*/
@MainThread
- public abstract void removeObserver(@NonNull LifecycleObserver observer);
+ public abstract void removeObserver(LifecycleObserver observer);
/**
* Returns the current state of the Lifecycle.
@@ -194,7 +193,7 @@ public abstract class Lifecycle {
* @param state State to compare with
* @return true if this State is greater or equal to the given {@code state}
*/
- public boolean isAtLeast(@NonNull State state) {
+ public boolean isAtLeast(State state) {
return compareTo(state) >= 0;
}
}
diff --git a/android/arch/lifecycle/LifecycleDispatcher.java b/android/arch/lifecycle/LifecycleDispatcher.java
new file mode 100644
index 00000000..9fdec959
--- /dev/null
+++ b/android/arch/lifecycle/LifecycleDispatcher.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 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 android.arch.lifecycle;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
+import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
+import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+import static android.arch.lifecycle.Lifecycle.State.CREATED;
+
+import android.app.Activity;
+import android.app.Application;
+import android.arch.lifecycle.Lifecycle.State;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * When initialized, it hooks into the Activity callback of the Application and observes
+ * Activities. It is responsible to hook in child-fragments to activities and fragments to report
+ * their lifecycle events. Another responsibility of this class is to mark as stopped all lifecycle
+ * providers related to an activity as soon it is not safe to run a fragment transaction in this
+ * activity.
+ */
+class LifecycleDispatcher {
+
+ private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ + ".LifecycleDispatcher.report_fragment_tag";
+
+ private static AtomicBoolean sInitialized = new AtomicBoolean(false);
+
+ static void init(Context context) {
+ if (sInitialized.getAndSet(true)) {
+ return;
+ }
+ ((Application) context.getApplicationContext())
+ .registerActivityLifecycleCallbacks(new DispatcherActivityCallback());
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @VisibleForTesting
+ static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {
+ private final FragmentCallback mFragmentCallback;
+
+ DispatcherActivityCallback() {
+ mFragmentCallback = new FragmentCallback();
+ }
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+ if (activity instanceof FragmentActivity) {
+ ((FragmentActivity) activity).getSupportFragmentManager()
+ .registerFragmentLifecycleCallbacks(mFragmentCallback, true);
+ }
+ ReportFragment.injectIfNeededIn(activity);
+ }
+
+ @Override
+ public void onActivityStopped(Activity activity) {
+ if (activity instanceof FragmentActivity) {
+ markState((FragmentActivity) activity, CREATED);
+ }
+ }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
+ if (activity instanceof FragmentActivity) {
+ markState((FragmentActivity) activity, CREATED);
+ }
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public static class DestructionReportFragment extends Fragment {
+ @Override
+ public void onPause() {
+ super.onPause();
+ dispatch(ON_PAUSE);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ dispatch(ON_STOP);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ dispatch(ON_DESTROY);
+ }
+
+ protected void dispatch(Lifecycle.Event event) {
+ dispatchIfLifecycleOwner(getParentFragment(), event);
+ }
+ }
+
+ private static void markState(FragmentManager manager, State state) {
+ Collection fragments = manager.getFragments();
+ if (fragments == null) {
+ return;
+ }
+ for (Fragment fragment : fragments) {
+ if (fragment == null) {
+ continue;
+ }
+ markStateIn(fragment, state);
+ if (fragment.isAdded()) {
+ markState(fragment.getChildFragmentManager(), state);
+ }
+ }
+ }
+
+ private static void markStateIn(Object object, State state) {
+ if (object instanceof LifecycleRegistryOwner) {
+ LifecycleRegistry registry = ((LifecycleRegistryOwner) object).getLifecycle();
+ registry.markState(state);
+ }
+ }
+
+ private static void markState(FragmentActivity activity, State state) {
+ markStateIn(activity, state);
+ markState(activity.getSupportFragmentManager(), state);
+ }
+
+ private static void dispatchIfLifecycleOwner(Fragment fragment, Lifecycle.Event event) {
+ if (fragment instanceof LifecycleRegistryOwner) {
+ ((LifecycleRegistryOwner) fragment).getLifecycle().handleLifecycleEvent(event);
+ }
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ @VisibleForTesting
+ static class FragmentCallback extends FragmentManager.FragmentLifecycleCallbacks {
+
+ @Override
+ public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {
+ dispatchIfLifecycleOwner(f, ON_CREATE);
+
+ if (!(f instanceof LifecycleRegistryOwner)) {
+ return;
+ }
+
+ if (f.getChildFragmentManager().findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
+ f.getChildFragmentManager().beginTransaction().add(new DestructionReportFragment(),
+ REPORT_FRAGMENT_TAG).commit();
+ }
+ }
+
+ @Override
+ public void onFragmentStarted(FragmentManager fm, Fragment f) {
+ dispatchIfLifecycleOwner(f, ON_START);
+ }
+
+ @Override
+ public void onFragmentResumed(FragmentManager fm, Fragment f) {
+ dispatchIfLifecycleOwner(f, ON_RESUME);
+ }
+ }
+}
diff --git a/android/arch/lifecycle/LifecycleOwner.java b/android/arch/lifecycle/LifecycleOwner.java
index 068bac1b..934cf3a2 100644
--- a/android/arch/lifecycle/LifecycleOwner.java
+++ b/android/arch/lifecycle/LifecycleOwner.java
@@ -16,8 +16,6 @@
package android.arch.lifecycle;
-import android.support.annotation.NonNull;
-
/**
* A class that has an Android lifecycle. These events can be used by custom components to
* handle lifecycle changes without implementing any code inside the Activity or the Fragment.
@@ -31,6 +29,5 @@ public interface LifecycleOwner {
*
* @return The lifecycle of the provider.
*/
- @NonNull
Lifecycle getLifecycle();
}
diff --git a/android/arch/lifecycle/LifecycleRegistry.java b/android/arch/lifecycle/LifecycleRegistry.java
index bf8aff79..b83e6b8a 100644
--- a/android/arch/lifecycle/LifecycleRegistry.java
+++ b/android/arch/lifecycle/LifecycleRegistry.java
@@ -29,12 +29,9 @@ import static android.arch.lifecycle.Lifecycle.State.RESUMED;
import static android.arch.lifecycle.Lifecycle.State.STARTED;
import android.arch.core.internal.FastSafeIterableMap;
-import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.util.Log;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map.Entry;
@@ -47,8 +44,6 @@ import java.util.Map.Entry;
*/
public class LifecycleRegistry extends Lifecycle {
- private static final String LOG_TAG = "LifecycleRegistry";
-
/**
* Custom list that keeps observers and can handle removals / additions during traversal.
*
@@ -64,12 +59,8 @@ public class LifecycleRegistry extends Lifecycle {
private State mState;
/**
* The provider that owns this Lifecycle.
- * Only WeakReference on LifecycleOwner is kept, so if somebody leaks Lifecycle, they won't leak
- * the whole Fragment / Activity. However, to leak Lifecycle object isn't great idea neither,
- * because it keeps strong references on all other listeners, so you'll leak all of them as
- * well.
*/
- private final WeakReference mLifecycleOwner;
+ private final LifecycleOwner mLifecycleOwner;
private int mAddingObserverCounter = 0;
@@ -95,19 +86,19 @@ public class LifecycleRegistry extends Lifecycle {
* @param provider The owner LifecycleOwner
*/
public LifecycleRegistry(@NonNull LifecycleOwner provider) {
- mLifecycleOwner = new WeakReference<>(provider);
+ mLifecycleOwner = provider;
mState = INITIALIZED;
}
/**
- * Moves the Lifecycle to the given state and dispatches necessary events to the observers.
+ * Only marks the current state as the given value. It doesn't dispatch any event to its
+ * listeners.
*
* @param state new state
*/
@SuppressWarnings("WeakerAccess")
- @MainThread
- public void markState(@NonNull State state) {
- moveToState(state);
+ public void markState(State state) {
+ mState = state;
}
/**
@@ -118,16 +109,8 @@ public class LifecycleRegistry extends Lifecycle {
*
* @param event The event that was received
*/
- public void handleLifecycleEvent(@NonNull Lifecycle.Event event) {
- State next = getStateAfter(event);
- moveToState(next);
- }
-
- private void moveToState(State next) {
- if (mState == next) {
- return;
- }
- mState = next;
+ public void handleLifecycleEvent(Lifecycle.Event event) {
+ mState = getStateAfter(event);
if (mHandlingEvent || mAddingObserverCounter != 0) {
mNewEventOccurred = true;
// we will figure out what to do on upper level.
@@ -157,7 +140,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void addObserver(@NonNull LifecycleObserver observer) {
+ public void addObserver(LifecycleObserver observer) {
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);
@@ -165,19 +148,15 @@ public class LifecycleRegistry extends Lifecycle {
if (previous != null) {
return;
}
- LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
- if (lifecycleOwner == null) {
- // it is null we should be destroyed. Fallback quickly
- return;
- }
boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
+
State targetState = calculateTargetState(observer);
mAddingObserverCounter++;
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
- statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
+ statefulObserver.dispatchEvent(mLifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
@@ -199,7 +178,7 @@ public class LifecycleRegistry extends Lifecycle {
}
@Override
- public void removeObserver(@NonNull LifecycleObserver observer) {
+ public void removeObserver(LifecycleObserver observer) {
// we consciously decided not to send destruction events here in opposition to addObserver.
// Our reasons for that:
// 1. These events haven't yet happened at all. In contrast to events in addObservers, that
@@ -279,7 +258,7 @@ public class LifecycleRegistry extends Lifecycle {
throw new IllegalArgumentException("Unexpected state value " + state);
}
- private void forwardPass(LifecycleOwner lifecycleOwner) {
+ private void forwardPass() {
Iterator> ascendingIterator =
mObserverMap.iteratorWithAdditions();
while (ascendingIterator.hasNext() && !mNewEventOccurred) {
@@ -288,13 +267,13 @@ public class LifecycleRegistry extends Lifecycle {
while ((observer.mState.compareTo(mState) < 0 && !mNewEventOccurred
&& mObserverMap.contains(entry.getKey()))) {
pushParentState(observer.mState);
- observer.dispatchEvent(lifecycleOwner, upEvent(observer.mState));
+ observer.dispatchEvent(mLifecycleOwner, upEvent(observer.mState));
popParentState();
}
}
}
- private void backwardPass(LifecycleOwner lifecycleOwner) {
+ private void backwardPass() {
Iterator> descendingIterator =
mObserverMap.descendingIterator();
while (descendingIterator.hasNext() && !mNewEventOccurred) {
@@ -304,7 +283,7 @@ public class LifecycleRegistry extends Lifecycle {
&& mObserverMap.contains(entry.getKey()))) {
Event event = downEvent(observer.mState);
pushParentState(getStateAfter(event));
- observer.dispatchEvent(lifecycleOwner, event);
+ observer.dispatchEvent(mLifecycleOwner, event);
popParentState();
}
}
@@ -313,22 +292,16 @@ public class LifecycleRegistry extends Lifecycle {
// happens only on the top of stack (never in reentrance),
// so it doesn't have to take in account parents
private void sync() {
- LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
- if (lifecycleOwner == null) {
- Log.w(LOG_TAG, "LifecycleOwner is garbage collected, you shouldn't try dispatch "
- + "new events from it.");
- return;
- }
while (!isSynced()) {
mNewEventOccurred = false;
// no need to check eldest for nullability, because isSynced does it for us.
if (mState.compareTo(mObserverMap.eldest().getValue().mState) < 0) {
- backwardPass(lifecycleOwner);
+ backwardPass();
}
Entry newest = mObserverMap.newest();
if (!mNewEventOccurred && newest != null
&& mState.compareTo(newest.getValue().mState) > 0) {
- forwardPass(lifecycleOwner);
+ forwardPass();
}
}
mNewEventOccurred = false;
diff --git a/android/arch/lifecycle/LifecycleRegistryOwner.java b/android/arch/lifecycle/LifecycleRegistryOwner.java
index 0c67fefe..38eeb6d3 100644
--- a/android/arch/lifecycle/LifecycleRegistryOwner.java
+++ b/android/arch/lifecycle/LifecycleRegistryOwner.java
@@ -16,8 +16,6 @@
package android.arch.lifecycle;
-import android.support.annotation.NonNull;
-
/**
* @deprecated Use {@code android.support.v7.app.AppCompatActivity}
* which extends {@link LifecycleOwner}, so there are no use cases for this class.
@@ -25,7 +23,6 @@ import android.support.annotation.NonNull;
@SuppressWarnings({"WeakerAccess", "unused"})
@Deprecated
public interface LifecycleRegistryOwner extends LifecycleOwner {
- @NonNull
@Override
LifecycleRegistry getLifecycle();
}
diff --git a/android/arch/lifecycle/LifecycleRegistryTest.java b/android/arch/lifecycle/LifecycleRegistryTest.java
index 2a7bbad2..6506454d 100644
--- a/android/arch/lifecycle/LifecycleRegistryTest.java
+++ b/android/arch/lifecycle/LifecycleRegistryTest.java
@@ -566,25 +566,6 @@ public class LifecycleRegistryTest {
verify(observer).onCreate();
}
- private static void forceGc() {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
- }
-
- @Test
- public void goneLifecycleOwner() {
- fullyInitializeRegistry();
- mLifecycleOwner = null;
- forceGc();
- TestObserver observer = mock(TestObserver.class);
- mRegistry.addObserver(observer);
- verify(observer, never()).onCreate();
- verify(observer, never()).onStart();
- verify(observer, never()).onResume();
- }
-
private void dispatchEvent(Lifecycle.Event event) {
when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
mRegistry.handleLifecycleEvent(event);
diff --git a/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java b/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
new file mode 100644
index 00000000..ac278c0c
--- /dev/null
+++ b/android/arch/lifecycle/LifecycleRuntimeTrojanProvider.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 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 android.arch.lifecycle;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Internal class to initialize Lifecycles.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class LifecycleRuntimeTrojanProvider extends ContentProvider {
+ @Override
+ public boolean onCreate() {
+ LifecycleDispatcher.init(getContext());
+ ProcessLifecycleOwner.init(getContext());
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Cursor query(@NonNull Uri uri, String[] strings, String s, String[] strings1,
+ String s1) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getType(@NonNull Uri uri) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
+ return null;
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, String s, String[] strings) {
+ return 0;
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, ContentValues contentValues, String s, String[] strings) {
+ return 0;
+ }
+}
diff --git a/android/arch/lifecycle/LiveData.java b/android/arch/lifecycle/LiveData.java
index 5b09c32f..3aea6acb 100644
--- a/android/arch/lifecycle/LiveData.java
+++ b/android/arch/lifecycle/LiveData.java
@@ -1,410 +1,4 @@
-/*
- * Copyright (C) 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.
- */
-
+//LiveData interface for tests
package android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-
-import android.arch.core.executor.ArchTaskExecutor;
-import android.arch.core.internal.SafeIterableMap;
-import android.arch.lifecycle.Lifecycle.State;
-import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * LiveData is a data holder class that can be observed within a given lifecycle.
- * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and
- * this observer will be notified about modifications of the wrapped data only if the paired
- * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is
- * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via
- * {@link #observeForever(Observer)} is considered as always active and thus will be always notified
- * about modifications. For those observers, you should manually call
- * {@link #removeObserver(Observer)}.
- *
- * An observer added with a Lifecycle will be automatically removed if the corresponding
- * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for
- * activities and fragments where they can safely observe LiveData and not worry about leaks:
- * they will be instantly unsubscribed when they are destroyed.
- *
- *
- * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods
- * to get notified when number of active {@link Observer}s change between 0 and 1.
- * This allows LiveData to release any heavy resources when it does not have any Observers that
- * are actively observing.
- *
- * This class is designed to hold individual data fields of {@link ViewModel},
- * but can also be used for sharing data between different modules in your application
- * in a decoupled fashion.
- *
- * @param The type of data held by this instance
- * @see ViewModel
- */
-@SuppressWarnings({"WeakerAccess", "unused"})
-// TODO: Thread checks are too strict right now, we may consider automatically moving them to main
-// thread.
-public abstract class LiveData {
- private final Object mDataLock = new Object();
- static final int START_VERSION = -1;
- private static final Object NOT_SET = new Object();
-
- private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {
-
- private LifecycleRegistry mRegistry = init();
-
- private LifecycleRegistry init() {
- LifecycleRegistry registry = new LifecycleRegistry(this);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
- return registry;
- }
-
- @Override
- public Lifecycle getLifecycle() {
- return mRegistry;
- }
- };
-
- private SafeIterableMap, LifecycleBoundObserver> mObservers =
- new SafeIterableMap<>();
-
- // how many observers are in active state
- private int mActiveCount = 0;
- private volatile Object mData = NOT_SET;
- // when setData is called, we set the pending data and actual data swap happens on the main
- // thread
- private volatile Object mPendingData = NOT_SET;
- private int mVersion = START_VERSION;
-
- private boolean mDispatchingValue;
- @SuppressWarnings("FieldCanBeLocal")
- private boolean mDispatchInvalidated;
- private final Runnable mPostValueRunnable = new Runnable() {
- @Override
- public void run() {
- Object newValue;
- synchronized (mDataLock) {
- newValue = mPendingData;
- mPendingData = NOT_SET;
- }
- //noinspection unchecked
- setValue((T) newValue);
- }
- };
-
- private void considerNotify(LifecycleBoundObserver observer) {
- if (!observer.active) {
- return;
- }
- // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
- //
- // we still first check observer.active to keep it as the entrance for events. So even if
- // the observer moved to an active state, if we've not received that event, we better not
- // notify for a more predictable notification order.
- if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
- observer.activeStateChanged(false);
- return;
- }
- if (observer.lastVersion >= mVersion) {
- return;
- }
- observer.lastVersion = mVersion;
- //noinspection unchecked
- observer.observer.onChanged((T) mData);
- }
-
- private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
- if (mDispatchingValue) {
- mDispatchInvalidated = true;
- return;
- }
- mDispatchingValue = true;
- do {
- mDispatchInvalidated = false;
- if (initiator != null) {
- considerNotify(initiator);
- initiator = null;
- } else {
- for (Iterator, LifecycleBoundObserver>> iterator =
- mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
- considerNotify(iterator.next().getValue());
- if (mDispatchInvalidated) {
- break;
- }
- }
- }
- } while (mDispatchInvalidated);
- mDispatchingValue = false;
- }
-
- /**
- * Adds the given observer to the observers list within the lifespan of the given
- * owner. The events are dispatched on the main thread. If LiveData already has data
- * set, it will be delivered to the observer.
- *
- * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED}
- * or {@link Lifecycle.State#RESUMED} state (active).
- *
- * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will
- * automatically be removed.
- *
- * When data changes while the {@code owner} is not active, it will not receive any updates.
- * If it becomes active again, it will receive the last available data automatically.
- *
- * LiveData keeps a strong reference to the observer and the owner as long as the
- * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to
- * the observer & the owner.
- *
- * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData
- * ignores the call.
- *
- * If the given owner, observer tuple is already in the list, the call is ignored.
- * If the observer is already in the list with another owner, LiveData throws an
- * {@link IllegalArgumentException}.
- *
- * @param owner The LifecycleOwner which controls the observer
- * @param observer The observer that will receive the events
- */
- @MainThread
- public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- // ignore
- return;
- }
- LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
- LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
- if (existing != null && existing.owner != wrapper.owner) {
- throw new IllegalArgumentException("Cannot add the same observer"
- + " with different lifecycles");
- }
- if (existing != null) {
- return;
- }
- owner.getLifecycle().addObserver(wrapper);
- }
-
- /**
- * Adds the given observer to the observers list. This call is similar to
- * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which
- * is always active. This means that the given observer will receive all events and will never
- * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop
- * observing this LiveData.
- * While LiveData has one of such observers, it will be considered
- * as active.
- *
- * If the observer was already added with an owner to this LiveData, LiveData throws an
- * {@link IllegalArgumentException}.
- *
- * @param observer The observer that will receive the events
- */
- @MainThread
- public void observeForever(@NonNull Observer observer) {
- observe(ALWAYS_ON, observer);
- }
-
- /**
- * Removes the given observer from the observers list.
- *
- * @param observer The Observer to receive events.
- */
- @MainThread
- public void removeObserver(@NonNull final Observer observer) {
- assertMainThread("removeObserver");
- LifecycleBoundObserver removed = mObservers.remove(observer);
- if (removed == null) {
- return;
- }
- removed.owner.getLifecycle().removeObserver(removed);
- removed.activeStateChanged(false);
- }
-
- /**
- * Removes all observers that are tied to the given {@link LifecycleOwner}.
- *
- * @param owner The {@code LifecycleOwner} scope for the observers to be removed.
- */
- @MainThread
- public void removeObservers(@NonNull final LifecycleOwner owner) {
- assertMainThread("removeObservers");
- for (Map.Entry, LifecycleBoundObserver> entry : mObservers) {
- if (entry.getValue().owner == owner) {
- removeObserver(entry.getKey());
- }
- }
- }
-
- /**
- * Posts a task to a main thread to set the given value. So if you have a following code
- * executed in the main thread:
- *
- * liveData.postValue("a");
- * liveData.setValue("b");
- *
- * The value "b" would be set at first and later the main thread would override it with
- * the value "a".
- *
- * If you called this method multiple times before a main thread executed a posted task, only
- * the last value would be dispatched.
- *
- * @param value The new value
- */
- protected void postValue(T value) {
- boolean postTask;
- synchronized (mDataLock) {
- postTask = mPendingData == NOT_SET;
- mPendingData = value;
- }
- if (!postTask) {
- return;
- }
- ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
- }
-
- /**
- * Sets the value. If there are active observers, the value will be dispatched to them.
- *
- * This method must be called from the main thread. If you need set a value from a background
- * thread, you can use {@link #postValue(Object)}
- *
- * @param value The new value
- */
- @MainThread
- protected void setValue(T value) {
- assertMainThread("setValue");
- mVersion++;
- mData = value;
- dispatchingValue(null);
- }
-
- /**
- * Returns the current value.
- * Note that calling this method on a background thread does not guarantee that the latest
- * value set will be received.
- *
- * @return the current value
- */
- @Nullable
- public T getValue() {
- Object data = mData;
- if (data != NOT_SET) {
- //noinspection unchecked
- return (T) data;
- }
- return null;
- }
-
- int getVersion() {
- return mVersion;
- }
-
- /**
- * Called when the number of active observers change to 1 from 0.
- *
- * This callback can be used to know that this LiveData is being used thus should be kept
- * up to date.
- */
- protected void onActive() {
-
- }
-
- /**
- * Called when the number of active observers change from 1 to 0.
- *
- * This does not mean that there are no observers left, there may still be observers but their
- * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}
- * (like an Activity in the back stack).
- *
- * You can check if there are observers via {@link #hasObservers()}.
- */
- protected void onInactive() {
-
- }
-
- /**
- * Returns true if this LiveData has observers.
- *
- * @return true if this LiveData has observers
- */
- public boolean hasObservers() {
- return mObservers.size() > 0;
- }
-
- /**
- * Returns true if this LiveData has active observers.
- *
- * @return true if this LiveData has active observers
- */
- public boolean hasActiveObservers() {
- return mActiveCount > 0;
- }
-
- class LifecycleBoundObserver implements GenericLifecycleObserver {
- public final LifecycleOwner owner;
- public final Observer observer;
- public boolean active;
- public int lastVersion = START_VERSION;
-
- LifecycleBoundObserver(LifecycleOwner owner, Observer observer) {
- this.owner = owner;
- this.observer = observer;
- }
-
- @Override
- public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
- if (owner.getLifecycle().getCurrentState() == DESTROYED) {
- removeObserver(observer);
- return;
- }
- // immediately set active state, so we'd never dispatch anything to inactive
- // owner
- activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
- }
-
- void activeStateChanged(boolean newActive) {
- if (newActive == active) {
- return;
- }
- active = newActive;
- boolean wasInactive = LiveData.this.mActiveCount == 0;
- LiveData.this.mActiveCount += active ? 1 : -1;
- if (wasInactive && active) {
- onActive();
- }
- if (LiveData.this.mActiveCount == 0 && !active) {
- onInactive();
- }
- if (active) {
- dispatchingValue(this);
- }
- }
- }
-
- static boolean isActiveState(State state) {
- return state.isAtLeast(STARTED);
- }
-
- private void assertMainThread(String methodName) {
- if (!ArchTaskExecutor.getInstance().isMainThread()) {
- throw new IllegalStateException("Cannot invoke " + methodName + " on a background"
- + " thread");
- }
- }
+public class LiveData {
}
diff --git a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java b/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
deleted file mode 100644
index 836cfff0..00000000
--- a/android/arch/lifecycle/LiveDataOnSaveInstanceStateTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 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 android.arch.lifecycle;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import android.app.Instrumentation;
-import android.arch.lifecycle.testapp.CollectingSupportActivity;
-import android.arch.lifecycle.testapp.CollectingSupportFragment;
-import android.arch.lifecycle.testapp.NavigationDialogActivity;
-import android.content.Intent;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.SmallTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v4.app.FragmentActivity;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.atomic.AtomicInteger;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class LiveDataOnSaveInstanceStateTest {
- @Rule
- public ActivityTestRule mActivityTestRule =
- new ActivityTestRule<>(CollectingSupportActivity.class);
-
- @Test
- @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
- public void liveData_partiallyObscuredActivity_maxSdkM() throws Throwable {
- CollectingSupportActivity activity = mActivityTestRule.getActivity();
-
- liveData_partiallyObscuredLifecycleOwner_maxSdkM(activity);
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
- public void liveData_partiallyObscuredActivityWithFragment_maxSdkM() throws Throwable {
- CollectingSupportActivity activity = mActivityTestRule.getActivity();
- CollectingSupportFragment fragment = new CollectingSupportFragment();
- mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
-
- liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment);
- }
-
- @Test
- @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.M)
- public void liveData_partiallyObscuredActivityFragmentInFragment_maxSdkM() throws Throwable {
- CollectingSupportActivity activity = mActivityTestRule.getActivity();
- CollectingSupportFragment fragment = new CollectingSupportFragment();
- CollectingSupportFragment fragment2 = new CollectingSupportFragment();
- mActivityTestRule.runOnUiThread(() -> {
- activity.replaceFragment(fragment);
- fragment.replaceFragment(fragment2);
- });
-
- liveData_partiallyObscuredLifecycleOwner_maxSdkM(fragment2);
- }
-
- @Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
- public void liveData_partiallyObscuredActivity_minSdkN() throws Throwable {
- CollectingSupportActivity activity = mActivityTestRule.getActivity();
-
- liveData_partiallyObscuredLifecycleOwner_minSdkN(activity);
- }
-
- @Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
- public void liveData_partiallyObscuredActivityWithFragment_minSdkN() throws Throwable {
- CollectingSupportActivity activity = mActivityTestRule.getActivity();
- CollectingSupportFragment fragment = new CollectingSupportFragment();
- mActivityTestRule.runOnUiThread(() -> activity.replaceFragment(fragment));
-
- liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment);
- }
-
- @Test
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N)
- public void liveData_partiallyObscuredActivityFragmentInFragment_minSdkN() throws Throwable {
- CollectingSupportActivity activity = mActivityTestRule.getActivity();
- CollectingSupportFragment fragment = new CollectingSupportFragment();
- CollectingSupportFragment fragment2 = new CollectingSupportFragment();
- mActivityTestRule.runOnUiThread(() -> {
- activity.replaceFragment(fragment);
- fragment.replaceFragment(fragment2);
- });
-
- liveData_partiallyObscuredLifecycleOwner_minSdkN(fragment2);
- }
-
- private void liveData_partiallyObscuredLifecycleOwner_maxSdkM(LifecycleOwner lifecycleOwner)
- throws Throwable {
- final AtomicInteger atomicInteger = new AtomicInteger(0);
- MutableLiveData mutableLiveData = new MutableLiveData<>();
- mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
-
- TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
-
- mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
-
- final FragmentActivity dialogActivity = launchDialog();
-
- TestUtils.waitTillCreated(lifecycleOwner, mActivityTestRule);
-
- // Change the LiveData value and assert that the observer is not called given that the
- // lifecycle is in the CREATED state.
- mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
- assertThat(atomicInteger.get(), is(0));
-
- // Finish the dialog Activity, wait for the main activity to be resumed, and assert that
- // the observer's onChanged method is called.
- mActivityTestRule.runOnUiThread(dialogActivity::finish);
- TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
- assertThat(atomicInteger.get(), is(1));
- }
-
- private void liveData_partiallyObscuredLifecycleOwner_minSdkN(LifecycleOwner lifecycleOwner)
- throws Throwable {
- final AtomicInteger atomicInteger = new AtomicInteger(0);
- MutableLiveData mutableLiveData = new MutableLiveData<>();
- mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(0));
-
- TestUtils.waitTillResumed(lifecycleOwner, mActivityTestRule);
-
- mutableLiveData.observe(lifecycleOwner, atomicInteger::set);
-
- // Launch the NavigationDialogActivity, partially obscuring the activity, and wait for the
- // lifecycleOwner to hit onPause (or enter the STARTED state). On API 24 and above, this
- // onPause should be the last lifecycle method called (and the STARTED state should be the
- // final resting state).
- launchDialog();
- TestUtils.waitTillStarted(lifecycleOwner, mActivityTestRule);
-
- // Change the LiveData's value and verify that the observer's onChanged method is called
- // since we are in the STARTED state.
- mActivityTestRule.runOnUiThread(() -> mutableLiveData.setValue(1));
- assertThat(atomicInteger.get(), is(1));
- }
-
- private FragmentActivity launchDialog() throws Throwable {
- Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
- NavigationDialogActivity.class.getCanonicalName(), null, false);
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- instrumentation.addMonitor(monitor);
-
- FragmentActivity activity = mActivityTestRule.getActivity();
- // helps with less flaky API 16 tests
- Intent intent = new Intent(activity, NavigationDialogActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- activity.startActivity(intent);
- FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
- TestUtils.waitTillResumed(fragmentActivity, mActivityTestRule);
- return fragmentActivity;
- }
-}
diff --git a/android/arch/lifecycle/LiveDataTest.java b/android/arch/lifecycle/LiveDataTest.java
index 647d5d7a..9f0b4257 100644
--- a/android/arch/lifecycle/LiveDataTest.java
+++ b/android/arch/lifecycle/LiveDataTest.java
@@ -53,29 +53,18 @@ import org.mockito.Mockito;
public class LiveDataTest {
private PublicLiveData mLiveData;
private LifecycleOwner mOwner;
- private LifecycleOwner mOwner2;
private LifecycleRegistry mRegistry;
- private LifecycleRegistry mRegistry2;
private MethodExec mActiveObserversChanged;
private boolean mInObserver;
@Before
public void init() {
mLiveData = new PublicLiveData<>();
-
- mActiveObserversChanged = mock(MethodExec.class);
- mLiveData.activeObserversChanged = mActiveObserversChanged;
-
mOwner = mock(LifecycleOwner.class);
-
mRegistry = new LifecycleRegistry(mOwner);
when(mOwner.getLifecycle()).thenReturn(mRegistry);
-
- mOwner2 = mock(LifecycleOwner.class);
-
- mRegistry2 = new LifecycleRegistry(mOwner2);
- when(mOwner2.getLifecycle()).thenReturn(mRegistry2);
-
+ mActiveObserversChanged = mock(MethodExec.class);
+ mLiveData.activeObserversChanged = mActiveObserversChanged;
mInObserver = false;
}
@@ -170,11 +159,14 @@ public class LiveDataTest {
@Test
public void testAddSameObserverIn2LifecycleOwners() {
Observer observer = (Observer) mock(Observer.class);
+ LifecycleOwner owner2 = mock(LifecycleOwner.class);
+ LifecycleRegistry registry2 = new LifecycleRegistry(owner2);
+ when(owner2.getLifecycle()).thenReturn(registry2);
mLiveData.observe(mOwner, observer);
Throwable throwable = null;
try {
- mLiveData.observe(mOwner2, observer);
+ mLiveData.observe(owner2, observer);
} catch (Throwable t) {
throwable = t;
}
@@ -464,210 +456,6 @@ public class LiveDataTest {
inOrder.verifyNoMoreInteractions();
}
- @Test
- public void setValue_neverActive_observerOnChangedNotCalled() {
- Observer observer = (Observer) mock(Observer.class);
- mLiveData.observe(mOwner, observer);
-
- mLiveData.setValue("1");
-
- verify(observer, never()).onChanged(anyString());
- }
-
- @Test
- public void setValue_twoObserversTwoStartedOwners_onChangedCalledOnBoth() {
- Observer observer1 = mock(Observer.class);
- Observer observer2 = mock(Observer.class);
-
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
-
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
- mLiveData.setValue("1");
-
- verify(observer1).onChanged("1");
- verify(observer2).onChanged("1");
- }
-
- @Test
- public void setValue_twoObserversOneStartedOwner_onChangedCalledOnOneCorrectObserver() {
- Observer observer1 = mock(Observer.class);
- Observer observer2 = mock(Observer.class);
-
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
-
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
- mLiveData.setValue("1");
-
- verify(observer1).onChanged("1");
- verify(observer2, never()).onChanged(anyString());
- }
-
- @Test
- public void setValue_twoObserversBothStartedAfterSetValue_onChangedCalledOnBoth() {
- Observer observer1 = mock(Observer.class);
- Observer observer2 = mock(Observer.class);
-
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
-
- mLiveData.setValue("1");
-
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
- verify(observer1).onChanged("1");
- verify(observer1).onChanged("1");
- }
-
- @Test
- public void setValue_twoObserversOneStartedAfterSetValue_onChangedCalledOnCorrectObserver() {
- Observer observer1 = mock(Observer.class);
- Observer observer2 = mock(Observer.class);
-
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
-
- mLiveData.setValue("1");
-
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
- verify(observer1).onChanged("1");
- verify(observer2, never()).onChanged(anyString());
- }
-
- @Test
- public void setValue_twoObserversOneStarted_liveDataBecomesActive() {
- Observer observer1 = mock(Observer.class);
- Observer observer2 = mock(Observer.class);
-
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
-
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
- verify(mActiveObserversChanged).onCall(true);
- }
-
- @Test
- public void setValue_twoObserversOneStopped_liveDataStaysActive() {
- Observer observer1 = mock(Observer.class);
- Observer observer2 = mock(Observer.class);
-
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
-
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
- verify(mActiveObserversChanged).onCall(true);
-
- reset(mActiveObserversChanged);
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
-
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- }
-
- /**
- * Verifies that if a lifecycle's state changes without an event, and changes to something that
- * LiveData would become inactive in response to, LiveData will detect the change upon new data
- * being set and become inactive. Also verifies that once the lifecycle enters into a state
- * that LiveData should become active to, that it does indeed become active.
- */
- @Test
- public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_oneObserver() {
- Observer observer = (Observer) mock(Observer.class);
- mLiveData.observe(mOwner, observer);
-
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
- // Marking state as CREATED should call onInactive.
- reset(mActiveObserversChanged);
- mRegistry.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged).onCall(false);
- reset(mActiveObserversChanged);
-
- // Setting a new value should trigger LiveData to realize the Lifecycle it is observing
- // is in a state where the LiveData should be inactive, so the LiveData will call onInactive
- // and the Observer shouldn't be affected.
- mLiveData.setValue("1");
-
- // state is already CREATED so should not call again
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer, never()).onChanged(anyString());
-
- // Sanity check. Because we've only marked the state as CREATED, sending ON_START
- // should re-dispatch events.
- reset(mActiveObserversChanged);
- reset(observer);
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- verify(mActiveObserversChanged).onCall(true);
- verify(observer).onChanged("1");
- }
-
- /**
- * This test verifies that LiveData will detect changes in LifecycleState that would make it
- * inactive upon the setting of new data, but only if all of the Lifecycles it's observing
- * are all in those states. It also makes sure that once it is inactive, that it will become
- * active again once one of the lifecycles it's observing moves to an appropriate state.
- */
- @Test
- public void liveDataActiveStateIsManagedCorrectlyWithoutEvent_twoObservers() {
- Observer observer1 = mock(Observer.class);
- Observer observer2 = mock(Observer.class);
-
- mLiveData.observe(mOwner, observer1);
- mLiveData.observe(mOwner2, observer2);
-
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
- mRegistry2.handleLifecycleEvent(Lifecycle.Event.ON_START);
-
- // Marking the state to created won't change LiveData to be inactive.
- reset(mActiveObserversChanged);
- mRegistry.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
-
- // After setting a value, the LiveData will stay active because there is still a STARTED
- // lifecycle being observed. The one Observer associated with the STARTED lifecycle will
- // also have been called, but the other Observer will not have been called.
- reset(observer1);
- reset(observer2);
- mLiveData.setValue("1");
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer1, never()).onChanged(anyString());
- verify(observer2).onChanged("1");
-
- // Now we set the other Lifecycle to be inactive, live data should become inactive.
- reset(observer1);
- reset(observer2);
- mRegistry2.markState(Lifecycle.State.CREATED);
- verify(mActiveObserversChanged).onCall(false);
- verify(observer1, never()).onChanged(anyString());
- verify(observer2, never()).onChanged(anyString());
-
- // Now we post another value, because both lifecycles are in the Created state, live data
- // will not dispatch any values
- reset(mActiveObserversChanged);
- mLiveData.setValue("2");
- verify(mActiveObserversChanged, never()).onCall(anyBoolean());
- verify(observer1, never()).onChanged(anyString());
- verify(observer2, never()).onChanged(anyString());
-
- // Now that the first Lifecycle has been moved back to the Resumed state, the LiveData will
- // be made active and it's associated Observer will be called with the new value, but the
- // Observer associated with the Lifecycle that is still in the Created state won't be
- // called.
- reset(mActiveObserversChanged);
- mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
- verify(mActiveObserversChanged).onCall(true);
- verify(observer1).onChanged("2");
- verify(observer2, never()).onChanged(anyString());
- }
-
@SuppressWarnings("WeakerAccess")
static class PublicLiveData extends LiveData {
// cannot spy due to internal calls
diff --git a/android/arch/lifecycle/MediatorLiveData.java b/android/arch/lifecycle/MediatorLiveData.java
index 58647394..672b3a3b 100644
--- a/android/arch/lifecycle/MediatorLiveData.java
+++ b/android/arch/lifecycle/MediatorLiveData.java
@@ -19,49 +19,16 @@ package android.arch.lifecycle;
import android.arch.core.internal.SafeIterableMap;
import android.support.annotation.CallSuper;
import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Map;
/**
- * {@link LiveData} subclass which may observe other {@code LiveData} objects and react on
+ * {@link LiveData} subclass which may observer other {@code LiveData} objects and react on
* {@code OnChanged} events from them.
*
* This class correctly propagates its active/inactive states down to source {@code LiveData}
* objects.
- *
- * Consider the following scenario: we have 2 instances of {@code LiveData}, let's name them
- * {@code liveData1} and {@code liveData2}, and we want to merge their emissions in one object:
- * {@code liveDataMerger}. Then, {@code liveData1} and {@code liveData2} will become sources for
- * the {@code MediatorLiveData liveDataMerger} and every time {@code onChanged} callback
- * is called for either of them, we set a new value in {@code liveDataMerger}.
- *
- *
- * LiveData liveData1 = ...;
- * LiveData liveData2 = ...;
- *
- * MediatorLiveData liveDataMerger = new MediatorLiveData<>();
- * liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
- * liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
- *
- *
- * Let's consider that we only want 10 values emitted by {@code liveData1}, to be
- * merged in the {@code liveDataMerger}. Then, after 10 values, we can stop listening to {@code
- * liveData1} and remove it as a source.
- *
- * liveDataMerger.addSource(liveData1, new Observer() {
- * private int count = 1;
- *
- * {@literal @}Override public void onChanged(@Nullable Integer s) {
- * count++;
- * liveDataMerger.setValue(s);
- * if (count > 10) {
- * liveDataMerger.removeSource(liveData1);
- * }
- * }
- * });
- *
*
* @param The type of data hold by this instance
*/
@@ -82,7 +49,7 @@ public class MediatorLiveData extends MutableLiveData {
* @param The type of data hold by {@code source} LiveData
*/
@MainThread
- public void addSource(@NonNull LiveData source, @NonNull Observer onChanged) {
+ public void addSource(LiveData source, Observer onChanged) {
Source e = new Source<>(source, onChanged);
Source> existing = mSources.putIfAbsent(source, e);
if (existing != null && existing.mObserver != onChanged) {
@@ -104,7 +71,7 @@ public class MediatorLiveData extends MutableLiveData {
* @param the type of data hold by {@code source} LiveData
*/
@MainThread
- public void removeSource(@NonNull LiveData toRemote) {
+ public void removeSource(LiveData toRemote) {
Source> source = mSources.remove(toRemote);
if (source != null) {
source.unplug();
diff --git a/android/arch/lifecycle/MissingClassTest.java b/android/arch/lifecycle/MissingClassTest.java
deleted file mode 100644
index 81a07564..00000000
--- a/android/arch/lifecycle/MissingClassTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 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 android.arch.lifecycle;
-
-import android.app.PictureInPictureParams;
-import android.os.Build;
-import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.N_MR1)
-@SmallTest
-public class MissingClassTest {
- public static class ObserverWithMissingClasses {
- @SuppressWarnings("unused")
- public void newApiMethod(PictureInPictureParams params) {}
-
- @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
- public void onResume() {}
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testMissingApi() {
- new ReflectiveGenericLifecycleObserver(new ObserverWithMissingClasses());
- }
-}
diff --git a/android/arch/lifecycle/PartiallyCoveredActivityTest.java b/android/arch/lifecycle/PartiallyCoveredActivityTest.java
deleted file mode 100644
index 07a9dc5a..00000000
--- a/android/arch/lifecycle/PartiallyCoveredActivityTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 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 android.arch.lifecycle;
-
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.CREATE;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.DESTROY;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.PAUSE;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.RESUME;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.START;
-import static android.arch.lifecycle.TestUtils.OrderedTuples.STOP;
-import static android.arch.lifecycle.TestUtils.flatMap;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-
-import android.app.Instrumentation;
-import android.arch.lifecycle.testapp.CollectingLifecycleOwner;
-import android.arch.lifecycle.testapp.CollectingSupportActivity;
-import android.arch.lifecycle.testapp.CollectingSupportFragment;
-import android.arch.lifecycle.testapp.NavigationDialogActivity;
-import android.arch.lifecycle.testapp.TestEvent;
-import android.content.Intent;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.rule.ActivityTestRule;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.util.Pair;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-/**
- * Runs tests about the state when an activity is partially covered by another activity. Pre
- * API 24, framework behavior changes so the test rely on whether state is saved or not and makes
- * assertions accordingly.
- */
-@SuppressWarnings("unchecked")
-@RunWith(Parameterized.class)
-@LargeTest
-public class PartiallyCoveredActivityTest {
- private static final List[] IF_SAVED = new List[]{
- // when overlaid
- flatMap(CREATE, START, RESUME, PAUSE,
- singletonList(new Pair<>(LIFECYCLE_EVENT, ON_STOP))),
- // post dialog dismiss
- asList(new Pair<>(OWNER_CALLBACK, ON_RESUME),
- new Pair<>(LIFECYCLE_EVENT, ON_START),
- new Pair<>(LIFECYCLE_EVENT, ON_RESUME)),
- // post finish
- flatMap(PAUSE, STOP, DESTROY)};
-
- private static final List[] IF_NOT_SAVED = new List[]{
- // when overlaid
- flatMap(CREATE, START, RESUME, PAUSE),
- // post dialog dismiss
- flatMap(RESUME),
- // post finish
- flatMap(PAUSE, STOP, DESTROY)};
-
- private static final boolean sShouldSave = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;
- private static final List>[] EXPECTED =
- sShouldSave ? IF_SAVED : IF_NOT_SAVED;
-
- @Rule
- public ActivityTestRule activityRule =
- new ActivityTestRule(
- CollectingSupportActivity.class) {
- @Override
- protected Intent getActivityIntent() {
- // helps with less flaky API 16 tests
- Intent intent = new Intent(InstrumentationRegistry.getTargetContext(),
- CollectingSupportActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- return intent;
- }
- };
- private final boolean mDismissDialog;
-
- @Parameterized.Parameters(name = "dismissDialog_{0}")
- public static List dismissDialog() {
- return asList(true, false);
- }
-
- public PartiallyCoveredActivityTest(boolean dismissDialog) {
- mDismissDialog = dismissDialog;
- }
-
- @Test
- public void coveredWithDialog_activity() throws Throwable {
- final CollectingSupportActivity activity = activityRule.getActivity();
- runTest(activity);
- }
-
- @Test
- public void coveredWithDialog_fragment() throws Throwable {
- CollectingSupportFragment fragment = new CollectingSupportFragment();
- activityRule.runOnUiThread(() -> activityRule.getActivity().replaceFragment(fragment));
- runTest(fragment);
- }
-
- @Test
- public void coveredWithDialog_childFragment() throws Throwable {
- CollectingSupportFragment parentFragment = new CollectingSupportFragment();
- CollectingSupportFragment childFragment = new CollectingSupportFragment();
- activityRule.runOnUiThread(() -> {
- activityRule.getActivity().replaceFragment(parentFragment);
- parentFragment.replaceFragment(childFragment);
- });
- runTest(childFragment);
- }
-
- private void runTest(CollectingLifecycleOwner owner) throws Throwable {
- TestUtils.waitTillResumed(owner, activityRule);
- FragmentActivity dialog = launchDialog();
- assertStateSaving();
- waitForIdle();
- assertThat(owner.copyCollectedEvents(), is(EXPECTED[0]));
- List> expected;
- if (mDismissDialog) {
- dialog.finish();
- TestUtils.waitTillResumed(activityRule.getActivity(), activityRule);
- assertThat(owner.copyCollectedEvents(), is(flatMap(EXPECTED[0], EXPECTED[1])));
- expected = flatMap(EXPECTED[0], EXPECTED[1], EXPECTED[2]);
- } else {
- expected = flatMap(CREATE, START, RESUME, PAUSE, STOP, DESTROY);
- }
- CollectingSupportActivity activity = activityRule.getActivity();
- activityRule.finishActivity();
- TestUtils.waitTillDestroyed(activity, activityRule);
- assertThat(owner.copyCollectedEvents(), is(expected));
- }
-
- // test sanity
- private void assertStateSaving() throws ExecutionException, InterruptedException {
- final CollectingSupportActivity activity = activityRule.getActivity();
- if (sShouldSave) {
- // state should be saved. wait for it to be saved
- assertThat("test sanity",
- activity.waitForStateSave(20), is(true));
- assertThat("test sanity", activity.getSupportFragmentManager()
- .isStateSaved(), is(true));
- } else {
- // should should not be saved
- assertThat("test sanity", activity.getSupportFragmentManager()
- .isStateSaved(), is(false));
- }
- }
-
- private void waitForIdle() {
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- private FragmentActivity launchDialog() throws Throwable {
- Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
- NavigationDialogActivity.class.getCanonicalName(), null, false);
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- instrumentation.addMonitor(monitor);
-
- FragmentActivity activity = activityRule.getActivity();
-
- Intent intent = new Intent(activity, NavigationDialogActivity.class);
- // disabling animations helps with less flaky API 16 tests
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- activity.startActivity(intent);
- FragmentActivity fragmentActivity = (FragmentActivity) monitor.waitForActivity();
- TestUtils.waitTillResumed(fragmentActivity, activityRule);
- return fragmentActivity;
- }
-}
diff --git a/android/arch/lifecycle/ProcessLifecycleOwner.java b/android/arch/lifecycle/ProcessLifecycleOwner.java
index 179e2c47..e2a12563 100644
--- a/android/arch/lifecycle/ProcessLifecycleOwner.java
+++ b/android/arch/lifecycle/ProcessLifecycleOwner.java
@@ -22,7 +22,6 @@ import android.arch.lifecycle.ReportFragment.ActivityInitializationListener;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
-import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
/**
@@ -157,8 +156,7 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
app.registerActivityLifecycleCallbacks(new EmptyActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
- ReportFragment.injectIfNeededIn(activity);
- ReportFragment.get(activity).setProcessListener(mInitializationListener);
+ ReportFragment .get(activity).setProcessListener(mInitializationListener);
}
@Override
@@ -173,7 +171,6 @@ public class ProcessLifecycleOwner implements LifecycleOwner {
});
}
- @NonNull
@Override
public Lifecycle getLifecycle() {
return mRegistry;
diff --git a/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java b/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
deleted file mode 100644
index 8ba297fe..00000000
--- a/android/arch/lifecycle/ProcessLifecycleOwnerInitializer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 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 android.arch.lifecycle;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
-
-/**
- * Internal class to initialize Lifecycles.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class ProcessLifecycleOwnerInitializer extends ContentProvider {
- @Override
- public boolean onCreate() {
- ProcessLifecycleOwner.init(getContext());
- return true;
- }
-
- @Nullable
- @Override
- public Cursor query(@NonNull Uri uri, String[] strings, String s, String[] strings1,
- String s1) {
- return null;
- }
-
- @Nullable
- @Override
- public String getType(@NonNull Uri uri) {
- return null;
- }
-
- @Nullable
- @Override
- public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
- return null;
- }
-
- @Override
- public int delete(@NonNull Uri uri, String s, String[] strings) {
- return 0;
- }
-
- @Override
- public int update(@NonNull Uri uri, ContentValues contentValues, String s, String[] strings) {
- return 0;
- }
-}
diff --git a/android/arch/lifecycle/ProcessOwnerTest.java b/android/arch/lifecycle/ProcessOwnerTest.java
index 77baf94c..37bdcdb4 100644
--- a/android/arch/lifecycle/ProcessOwnerTest.java
+++ b/android/arch/lifecycle/ProcessOwnerTest.java
@@ -31,7 +31,6 @@ import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.testapp.NavigationDialogActivity;
import android.arch.lifecycle.testapp.NavigationTestActivityFirst;
import android.arch.lifecycle.testapp.NavigationTestActivitySecond;
-import android.arch.lifecycle.testapp.NonSupportActivity;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
@@ -95,22 +94,6 @@ public class ProcessOwnerTest {
checkProcessObserverSilent(secondActivity);
}
- @Test
- public void testNavigationToNonSupport() throws Throwable {
- FragmentActivity firstActivity = setupObserverOnResume();
- Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
- NonSupportActivity.class.getCanonicalName(), null, false);
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- instrumentation.addMonitor(monitor);
-
- Intent intent = new Intent(firstActivity, NonSupportActivity.class);
- firstActivity.finish();
- firstActivity.startActivity(intent);
- NonSupportActivity secondActivity = (NonSupportActivity) monitor.waitForActivity();
- assertThat("Failed to navigate", secondActivity, notNullValue());
- checkProcessObserverSilent(secondActivity);
- }
-
@Test
public void testRecreation() throws Throwable {
FragmentActivity activity = setupObserverOnResume();
@@ -181,11 +164,4 @@ public class ProcessOwnerTest {
activityTestRule.runOnUiThread(() ->
ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
}
-
- private void checkProcessObserverSilent(NonSupportActivity activity) throws Throwable {
- assertThat(activity.awaitResumedState(), is(true));
- assertThat(mObserver.mChangedState, is(false));
- activityTestRule.runOnUiThread(() ->
- ProcessLifecycleOwner.get().getLifecycle().removeObserver(mObserver));
- }
}
diff --git a/android/arch/lifecycle/ReportFragment.java b/android/arch/lifecycle/ReportFragment.java
index 16a89ce8..3e4ece82 100644
--- a/android/arch/lifecycle/ReportFragment.java
+++ b/android/arch/lifecycle/ReportFragment.java
@@ -28,6 +28,7 @@ import android.support.annotation.RestrictTo;
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class ReportFragment extends Fragment {
+
private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ ".LifecycleDispatcher.report_fragment_tag";
diff --git a/android/arch/lifecycle/TestUtils.java b/android/arch/lifecycle/TestUtils.java
index f7f9bbe5..f0214bfb 100644
--- a/android/arch/lifecycle/TestUtils.java
+++ b/android/arch/lifecycle/TestUtils.java
@@ -16,35 +16,16 @@
package android.arch.lifecycle;
-import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY;
-import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE;
-import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME;
-import static android.arch.lifecycle.Lifecycle.Event.ON_START;
-import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
-import static android.arch.lifecycle.Lifecycle.State.CREATED;
-import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
import static android.arch.lifecycle.Lifecycle.State.RESUMED;
-import static android.arch.lifecycle.Lifecycle.State.STARTED;
-import static android.arch.lifecycle.testapp.TestEvent.LIFECYCLE_EVENT;
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
-import android.arch.lifecycle.testapp.TestEvent;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
-import android.support.v4.util.Pair;
+import android.support.v4.app.FragmentActivity;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
class TestUtils {
@@ -80,88 +61,23 @@ class TestUtils {
return result;
}
- static void waitTillCreated(final LifecycleOwner owner, ActivityTestRule> activityRule)
- throws Throwable {
- waitTillState(owner, activityRule, CREATED);
- }
-
- static void waitTillStarted(final LifecycleOwner owner, ActivityTestRule> activityRule)
- throws Throwable {
- waitTillState(owner, activityRule, STARTED);
- }
-
- static void waitTillResumed(final LifecycleOwner owner, ActivityTestRule> activityRule)
- throws Throwable {
- waitTillState(owner, activityRule, RESUMED);
- }
-
- static void waitTillDestroyed(final LifecycleOwner owner, ActivityTestRule> activityRule)
- throws Throwable {
- waitTillState(owner, activityRule, DESTROYED);
- }
-
- static void waitTillState(final LifecycleOwner owner, ActivityTestRule> activityRule,
- Lifecycle.State state)
+ static void waitTillResumed(final FragmentActivity a, ActivityTestRule> activityRule)
throws Throwable {
final CountDownLatch latch = new CountDownLatch(1);
activityRule.runOnUiThread(() -> {
- Lifecycle.State currentState = owner.getLifecycle().getCurrentState();
- if (currentState == state) {
+ Lifecycle.State currentState = a.getLifecycle().getCurrentState();
+ if (currentState == RESUMED) {
latch.countDown();
- } else {
- owner.getLifecycle().addObserver(new LifecycleObserver() {
- @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
- public void onStateChanged(LifecycleOwner provider) {
- if (provider.getLifecycle().getCurrentState() == state) {
- latch.countDown();
- provider.getLifecycle().removeObserver(this);
- }
- }
- });
}
+ a.getLifecycle().addObserver(new LifecycleObserver() {
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ public void onStateChanged(LifecycleOwner provider) {
+ latch.countDown();
+ provider.getLifecycle().removeObserver(this);
+ }
+ });
});
- boolean latchResult = latch.await(1, TimeUnit.MINUTES);
- assertThat("expected " + state + " never happened. Current state:"
- + owner.getLifecycle().getCurrentState(), latchResult, is(true));
-
- // wait for another loop to ensure all observers are called
- activityRule.runOnUiThread(() -> {
- // do nothing
- });
+ latch.await();
}
- @SafeVarargs
- static List flatMap(List... items) {
- ArrayList result = new ArrayList<>();
- for (List item : items) {
- result.addAll(item);
- }
- return result;
- }
-
- /**
- * Event tuples of {@link TestEvent} and {@link Lifecycle.Event}
- * in the order they should arrive.
- */
- @SuppressWarnings("unchecked")
- static class OrderedTuples {
- static final List> CREATE =
- Arrays.asList(new Pair(OWNER_CALLBACK, ON_CREATE),
- new Pair(LIFECYCLE_EVENT, ON_CREATE));
- static final List> START =
- Arrays.asList(new Pair(OWNER_CALLBACK, ON_START),
- new Pair(LIFECYCLE_EVENT, ON_START));
- static final List> RESUME =
- Arrays.asList(new Pair(OWNER_CALLBACK, ON_RESUME),
- new Pair(LIFECYCLE_EVENT, ON_RESUME));
- static final List> PAUSE =
- Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_PAUSE),
- new Pair(OWNER_CALLBACK, ON_PAUSE));
- static final List> STOP =
- Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_STOP),
- new Pair(OWNER_CALLBACK, ON_STOP));
- static final List> DESTROY =
- Arrays.asList(new Pair(LIFECYCLE_EVENT, ON_DESTROY),
- new Pair(OWNER_CALLBACK, ON_DESTROY));
- }
}
diff --git a/android/arch/lifecycle/Transformations.java b/android/arch/lifecycle/Transformations.java
index c735f8ba..9ce9cbb7 100644
--- a/android/arch/lifecycle/Transformations.java
+++ b/android/arch/lifecycle/Transformations.java
@@ -18,7 +18,6 @@ package android.arch.lifecycle;
import android.arch.core.util.Function;
import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
@@ -61,8 +60,7 @@ public class Transformations {
* @return a LiveData which emits resulting values
*/
@MainThread
- public static LiveData map(@NonNull LiveData source,
- @NonNull final Function func) {
+ public static LiveData map(LiveData source, final Function func) {
final MediatorLiveData result = new MediatorLiveData<>();
result.addSource(source, new Observer() {
@Override
@@ -122,8 +120,8 @@ public class Transformations {
* @param a type of resulting LiveData
*/
@MainThread
- public static LiveData switchMap(@NonNull LiveData trigger,
- @NonNull final Function> func) {
+ public static LiveData switchMap(LiveData trigger,
+ final Function> func) {
final MediatorLiveData result = new MediatorLiveData<>();
result.addSource(trigger, new Observer() {
LiveData mSource;
diff --git a/android/arch/lifecycle/ViewModelProvider.java b/android/arch/lifecycle/ViewModelProvider.java
index 29cbab8e..7ef591f3 100644
--- a/android/arch/lifecycle/ViewModelProvider.java
+++ b/android/arch/lifecycle/ViewModelProvider.java
@@ -43,8 +43,7 @@ public class ViewModelProvider {
* @param The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
- @NonNull
- T create(@NonNull Class modelClass);
+ T create(Class modelClass);
}
private final Factory mFactory;
@@ -71,7 +70,7 @@ public class ViewModelProvider {
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
- public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
+ public ViewModelProvider(ViewModelStore store, Factory factory) {
mFactory = factory;
this.mViewModelStore = store;
}
@@ -89,8 +88,7 @@ public class ViewModelProvider {
* @param The type parameter for the ViewModel.
* @return A ViewModel that is an instance of the given type {@code T}.
*/
- @NonNull
- public T get(@NonNull Class modelClass) {
+ public T get(Class modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
@@ -138,9 +136,8 @@ public class ViewModelProvider {
*/
public static class NewInstanceFactory implements Factory {
- @NonNull
@Override
- public T create(@NonNull Class modelClass) {
+ public T create(Class modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
diff --git a/android/arch/lifecycle/ViewModelProviders.java b/android/arch/lifecycle/ViewModelProviders.java
index b4b20aa4..746162a9 100644
--- a/android/arch/lifecycle/ViewModelProviders.java
+++ b/android/arch/lifecycle/ViewModelProviders.java
@@ -139,9 +139,8 @@ public class ViewModelProviders {
mApplication = application;
}
- @NonNull
@Override
- public T create(@NonNull Class modelClass) {
+ public T create(Class modelClass) {
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//noinspection TryWithIdenticalCatches
try {
diff --git a/android/arch/lifecycle/ViewModelStoreOwner.java b/android/arch/lifecycle/ViewModelStoreOwner.java
index e26fa325..50583056 100644
--- a/android/arch/lifecycle/ViewModelStoreOwner.java
+++ b/android/arch/lifecycle/ViewModelStoreOwner.java
@@ -16,8 +16,6 @@
package android.arch.lifecycle;
-import android.support.annotation.NonNull;
-
/**
* A scope that owns {@link ViewModelStore}.
*
@@ -32,6 +30,5 @@ public interface ViewModelStoreOwner {
*
* @return a {@code ViewModelStore}
*/
- @NonNull
ViewModelStore getViewModelStore();
}
diff --git a/android/arch/lifecycle/ViewModelStores.java b/android/arch/lifecycle/ViewModelStores.java
index d7d769d6..8c17dd98 100644
--- a/android/arch/lifecycle/ViewModelStores.java
+++ b/android/arch/lifecycle/ViewModelStores.java
@@ -19,7 +19,6 @@ package android.arch.lifecycle;
import static android.arch.lifecycle.HolderFragment.holderFragmentFor;
import android.support.annotation.MainThread;
-import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
@@ -39,7 +38,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(@NonNull FragmentActivity activity) {
+ public static ViewModelStore of(FragmentActivity activity) {
return holderFragmentFor(activity).getViewModelStore();
}
@@ -50,7 +49,7 @@ public class ViewModelStores {
* @return a {@code ViewModelStore}
*/
@MainThread
- public static ViewModelStore of(@NonNull Fragment fragment) {
+ public static ViewModelStore of(Fragment fragment) {
return holderFragmentFor(fragment).getViewModelStore();
}
}
diff --git a/android/arch/lifecycle/testapp/CollectingActivity.java b/android/arch/lifecycle/testapp/CollectingActivity.java
new file mode 100644
index 00000000..6e243b6c
--- /dev/null
+++ b/android/arch/lifecycle/testapp/CollectingActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 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 android.arch.lifecycle.testapp;
+
+import android.arch.lifecycle.Lifecycle;
+import android.util.Pair;
+
+import java.util.List;
+
+/**
+ * For activities that collect their events.
+ */
+public interface CollectingActivity {
+ long TIMEOUT = 5;
+
+ /**
+ * Return collected events
+ *
+ * @return The list of collected events.
+ * @throws InterruptedException
+ */
+ List> waitForCollectedEvents() throws InterruptedException;
+}
diff --git a/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java b/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
deleted file mode 100644
index 4213cab9..00000000
--- a/android/arch/lifecycle/testapp/CollectingLifecycleOwner.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 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 android.arch.lifecycle.testapp;
-
-import android.arch.lifecycle.Lifecycle;
-import android.arch.lifecycle.LifecycleOwner;
-import android.support.v4.util.Pair;
-
-import java.util.List;
-
-/**
- * For activities that collect their events.
- */
-public interface CollectingLifecycleOwner extends LifecycleOwner {
- /**
- * Return a copy of currently collected events
- *
- * @return The list of collected events.
- * @throws InterruptedException
- */
- List> copyCollectedEvents();
-}
diff --git a/android/arch/lifecycle/testapp/CollectingSupportActivity.java b/android/arch/lifecycle/testapp/CollectingSupportActivity.java
deleted file mode 100644
index f38d4224..00000000
--- a/android/arch/lifecycle/testapp/CollectingSupportActivity.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 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 android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import android.arch.lifecycle.Lifecycle.Event;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentActivity;
-import android.support.v4.util.Pair;
-import android.widget.FrameLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * LifecycleRegistryOwner that extends FragmentActivity.
- */
-public class CollectingSupportActivity extends FragmentActivity implements
- CollectingLifecycleOwner {
-
- private final List> mCollectedEvents = new ArrayList<>();
- private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
- private CountDownLatch mSavedStateLatch = new CountDownLatch(1);
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- FrameLayout layout = new FrameLayout(this);
- layout.setId(R.id.fragment_container);
- setContentView(layout);
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_CREATE));
- getLifecycle().addObserver(mTestObserver);
- }
-
- /**
- * replaces the main content fragment w/ the given fragment.
- */
- public void replaceFragment(Fragment fragment) {
- getSupportFragmentManager()
- .beginTransaction()
- .add(R.id.fragment_container, fragment)
- .commitNow();
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_START));
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_RESUME));
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_DESTROY));
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_STOP));
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Event.ON_PAUSE));
- // helps with less flaky API 16 tests.
- overridePendingTransition(0, 0);
- }
-
- @Override
- public List> copyCollectedEvents() {
- return new ArrayList<>(mCollectedEvents);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- mSavedStateLatch.countDown();
- }
-
- /**
- * Waits for onSaveInstanceState to be called.
- */
- public boolean waitForStateSave(@SuppressWarnings("SameParameterValue") int seconds)
- throws InterruptedException {
- return mSavedStateLatch.await(seconds, TimeUnit.SECONDS);
- }
-}
diff --git a/android/arch/lifecycle/testapp/CollectingSupportFragment.java b/android/arch/lifecycle/testapp/CollectingSupportFragment.java
deleted file mode 100644
index 9bbbe165..00000000
--- a/android/arch/lifecycle/testapp/CollectingSupportFragment.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 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 android.arch.lifecycle.testapp;
-
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
-
-import android.annotation.SuppressLint;
-import android.arch.lifecycle.Lifecycle;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.v4.app.Fragment;
-import android.support.v4.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A support fragment that collects all of its events.
- */
-@SuppressLint("ValidFragment")
-public class CollectingSupportFragment extends Fragment implements CollectingLifecycleOwner {
- private final List> mCollectedEvents =
- new ArrayList<>();
- private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
- getLifecycle().addObserver(mTestObserver);
- }
-
- @Nullable
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- @Nullable Bundle savedInstanceState) {
- //noinspection ConstantConditions
- FrameLayout layout = new FrameLayout(container.getContext());
- layout.setId(R.id.child_fragment_container);
- return layout;
- }
-
- /**
- * Runs a replace fragment transaction with 'fragment' on this Fragment.
- */
- public void replaceFragment(Fragment fragment) {
- getChildFragmentManager()
- .beginTransaction()
- .add(R.id.child_fragment_container, fragment)
- .commitNow();
- }
-
- @Override
- public void onStart() {
- super.onStart();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
- }
-
- @Override
- public void onResume() {
- super.onResume();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
- }
-
- @Override
- public void onStop() {
- super.onStop();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
- }
-
- @Override
- public List> copyCollectedEvents() {
- return new ArrayList<>(mCollectedEvents);
- }
-}
diff --git a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
index cdf577c1..d8f4fb39 100644
--- a/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
+++ b/android/arch/lifecycle/testapp/FrameworkLifecycleRegistryActivity.java
@@ -16,29 +16,27 @@
package android.arch.lifecycle.testapp;
-import static android.arch.lifecycle.testapp.TestEvent.OWNER_CALLBACK;
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
import android.app.Activity;
import android.arch.lifecycle.Lifecycle;
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.util.Pair;
+import android.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* LifecycleRegistryOwner that extends framework activity.
*/
-@SuppressWarnings("deprecation")
public class FrameworkLifecycleRegistryActivity extends Activity implements
- LifecycleRegistryOwner, CollectingLifecycleOwner {
+ LifecycleRegistryOwner, CollectingActivity {
private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
- @NonNull
@Override
public LifecycleRegistry getLifecycle() {
return mLifecycleRegistry;
@@ -51,43 +49,49 @@ public class FrameworkLifecycleRegistryActivity extends Activity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_CREATE));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
getLifecycle().addObserver(mTestObserver);
}
@Override
protected void onStart() {
super.onStart();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_START));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
}
@Override
protected void onResume() {
super.onResume();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_RESUME));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
+ finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
mLatch.countDown();
}
@Override
protected void onStop() {
super.onStop();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_STOP));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
}
@Override
protected void onPause() {
super.onPause();
- mCollectedEvents.add(new Pair<>(OWNER_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
}
+ /**
+ * awaits for all events and returns them.
+ */
@Override
- public List> copyCollectedEvents() {
- return new ArrayList<>(mCollectedEvents);
+ public List> waitForCollectedEvents()
+ throws InterruptedException {
+ mLatch.await(TIMEOUT, TimeUnit.SECONDS);
+ return mCollectedEvents;
}
}
diff --git a/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
new file mode 100644
index 00000000..5f33c282
--- /dev/null
+++ b/android/arch/lifecycle/testapp/FullLifecycleTestActivity.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016 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 android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+
+import android.arch.lifecycle.Lifecycle;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Activity for testing full lifecycle
+ */
+public class FullLifecycleTestActivity extends FragmentActivity implements CollectingActivity {
+
+ private List> mCollectedEvents = new ArrayList<>();
+ private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+ private CountDownLatch mLatch = new CountDownLatch(1);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_CREATE));
+ getLifecycle().addObserver(mTestObserver);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_START));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_RESUME));
+ finish();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_DESTROY));
+ mLatch.countDown();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_STOP));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Lifecycle.Event.ON_PAUSE));
+ }
+
+ /**
+ * awaits for all events and returns them.
+ */
+ @Override
+ public List> waitForCollectedEvents()
+ throws InterruptedException {
+ mLatch.await(TIMEOUT, TimeUnit.SECONDS);
+ return mCollectedEvents;
+ }
+}
diff --git a/android/arch/lifecycle/testapp/MainActivity.java b/android/arch/lifecycle/testapp/MainActivity.java
new file mode 100644
index 00000000..b9d59142
--- /dev/null
+++ b/android/arch/lifecycle/testapp/MainActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 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 android.arch.lifecycle.testapp;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+/**
+ * Simple test activity
+ */
+public class MainActivity extends FragmentActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity);
+ }
+}
diff --git a/android/arch/lifecycle/testapp/NavigationDialogActivity.java b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
index 7d53528f..0ae94033 100644
--- a/android/arch/lifecycle/testapp/NavigationDialogActivity.java
+++ b/android/arch/lifecycle/testapp/NavigationDialogActivity.java
@@ -22,10 +22,4 @@ import android.support.v4.app.FragmentActivity;
* an activity with Dialog theme.
*/
public class NavigationDialogActivity extends FragmentActivity {
- @Override
- protected void onPause() {
- super.onPause();
- // helps with less flaky API 16 tests
- overridePendingTransition(0, 0);
- }
}
diff --git a/android/arch/lifecycle/testapp/NonSupportActivity.java b/android/arch/lifecycle/testapp/NonSupportActivity.java
deleted file mode 100644
index 835d846a..00000000
--- a/android/arch/lifecycle/testapp/NonSupportActivity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 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 android.arch.lifecycle.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Activity which doesn't extend FragmentActivity, to test ProcessLifecycleOwner because it
- * should work anyway.
- */
-public class NonSupportActivity extends Activity {
-
- private static final int TIMEOUT = 1; //secs
- private final Lock mLock = new ReentrantLock();
- private Condition mIsResumedCondition = mLock.newCondition();
- private boolean mIsResumed = false;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- mLock.lock();
- try {
- mIsResumed = true;
- mIsResumedCondition.signalAll();
- } finally {
- mLock.unlock();
- }
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- mLock.lock();
- try {
- mIsResumed = false;
- } finally {
- mLock.unlock();
- }
- }
-
- /**
- * awaits resumed state
- * @return
- * @throws InterruptedException
- */
- public boolean awaitResumedState() throws InterruptedException {
- mLock.lock();
- try {
- while (!mIsResumed) {
- if (!mIsResumedCondition.await(TIMEOUT, TimeUnit.SECONDS)) {
- return false;
- }
- }
- return true;
- } finally {
- mLock.unlock();
- }
- }
-}
diff --git a/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
new file mode 100644
index 00000000..c46c6d3e
--- /dev/null
+++ b/android/arch/lifecycle/testapp/SupportLifecycleRegistryActivity.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 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 android.arch.lifecycle.testapp;
+
+import static android.arch.lifecycle.testapp.TestEvent.ACTIVITY_CALLBACK;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleRegistry;
+import android.arch.lifecycle.LifecycleRegistryOwner;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * LifecycleRegistryOwner that extends FragmentActivity.
+ */
+public class SupportLifecycleRegistryActivity extends FragmentActivity implements
+ LifecycleRegistryOwner, CollectingActivity {
+ private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+ @Override
+ public LifecycleRegistry getLifecycle() {
+ return mLifecycleRegistry;
+ }
+
+ private List> mCollectedEvents = new ArrayList<>();
+ private TestObserver mTestObserver = new TestObserver(mCollectedEvents);
+ private CountDownLatch mLatch = new CountDownLatch(1);
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_CREATE));
+ getLifecycle().addObserver(mTestObserver);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_START));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_RESUME));
+ finish();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_DESTROY));
+ mLatch.countDown();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_STOP));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mCollectedEvents.add(new Pair<>(ACTIVITY_CALLBACK, Event.ON_PAUSE));
+ }
+
+ /**
+ * awaits for all events and returns them.
+ */
+ @Override
+ public List> waitForCollectedEvents() throws InterruptedException {
+ mLatch.await(TIMEOUT, TimeUnit.SECONDS);
+ return mCollectedEvents;
+ }
+}
diff --git a/android/arch/lifecycle/testapp/TestEvent.java b/android/arch/lifecycle/testapp/TestEvent.java
index 788045a2..0929f84a 100644
--- a/android/arch/lifecycle/testapp/TestEvent.java
+++ b/android/arch/lifecycle/testapp/TestEvent.java
@@ -17,6 +17,6 @@
package android.arch.lifecycle.testapp;
public enum TestEvent {
- OWNER_CALLBACK,
- LIFECYCLE_EVENT,
+ ACTIVITY_CALLBACK,
+ LIFECYCLE_EVENT
}
diff --git a/android/arch/lifecycle/testapp/TestObserver.java b/android/arch/lifecycle/testapp/TestObserver.java
index 00b8e16d..c6112396 100644
--- a/android/arch/lifecycle/testapp/TestObserver.java
+++ b/android/arch/lifecycle/testapp/TestObserver.java
@@ -28,7 +28,7 @@ import android.arch.lifecycle.Lifecycle.Event;
import android.arch.lifecycle.LifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.OnLifecycleEvent;
-import android.support.v4.util.Pair;
+import android.util.Pair;
import java.util.List;
diff --git a/android/arch/paging/BoundedDataSource.java b/android/arch/paging/BoundedDataSource.java
index 06564907..664ab16c 100644
--- a/android/arch/paging/BoundedDataSource.java
+++ b/android/arch/paging/BoundedDataSource.java
@@ -21,6 +21,7 @@ import android.support.annotation.RestrictTo;
import android.support.annotation.WorkerThread;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -74,6 +75,7 @@ public abstract class BoundedDataSource extends PositionalDataSource extends DataSource {
+ /**
+ * Number of items that this DataSource can provide in total, or COUNT_UNDEFINED.
+ *
+ * @return number of items that this DataSource can provide in total, or COUNT_UNDEFINED
+ * if difficult or undesired to compute.
+ */
+ public int countItems() {
+ return COUNT_UNDEFINED;
+ }
+
@Override
boolean isContiguous() {
return true;
}
- void loadInitial(Key key, int pageSize, boolean enablePlaceholders,
- PageResult.Receiver receiver) {
- NullPaddedList initial = loadInitial(key, pageSize, enablePlaceholders);
- if (initial != null) {
- receiver.onPageResult(new PageResult<>(
- PageResult.INIT,
- new Page(initial.mList),
- initial.getLeadingNullCount(),
- initial.getTrailingNullCount(),
- initial.getPositionOffset()));
- } else {
- receiver.onPageResult(new PageResult(
- PageResult.INIT, null, 0, 0, 0));
- }
- }
-
- void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
- PageResult.Receiver receiver) {
- List list = loadAfter(currentEndIndex, currentEndItem, pageSize);
-
- Page page = list != null
- ? new Page(list) : null;
-
- receiver.postOnPageResult(new PageResult<>(
- PageResult.APPEND, page, 0, 0, 0));
- }
-
- void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
- PageResult.Receiver receiver) {
- List list = loadBefore(currentBeginIndex, currentBeginItem, pageSize);
-
- Page page = list != null
- ? new Page(list) : null;
-
- receiver.postOnPageResult(new PageResult<>(
- PageResult.PREPEND, page, 0, 0, 0));
- }
-
- /**
- * Get the key from either the position, or item, or null if position/item invalid.
- *
- * Position may not match passed item's position - if trying to query the key from a position
- * that isn't yet loaded, a fallback item (last loaded item accessed) will be passed.
- */
- abstract Key getKey(int position, Value item);
-
- @Nullable
- abstract List loadAfterImpl(int currentEndIndex,
- @NonNull Value currentEndItem, int pageSize);
-
- @Nullable
- abstract List loadBeforeImpl(int currentBeginIndex,
- @NonNull Value currentBeginItem, int pageSize);
-
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@@ -92,7 +48,21 @@ public abstract class ContiguousDataSource extends DataSource loadInitial(
Key key, int initialLoadSize, boolean enablePlaceholders);
- /** @hide */
+ /**
+ * Load data after the given position / item.
+ *
+ * It's valid to return a different list size than the page size, if it's easier for this data
+ * source. It is generally safer to increase number loaded than reduce.
+ *
+ * @param currentEndIndex Load items after this index, starting with currentEndIndex + 1.
+ * @param currentEndItem Load items after this item, can be used for precise querying based on
+ * item contents.
+ * @param pageSize Suggested number of items to load.
+ * @return List of items, starting at position currentEndIndex + 1. Null if the data source is
+ * no longer valid, and should not be queried again.
+ *
+ * @hide
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -108,7 +78,24 @@ public abstract class ContiguousDataSource extends DataSource loadAfterImpl(int currentEndIndex,
+ @NonNull Value currentEndItem, int pageSize);
+
+ /**
+ * Load data before the given position / item.
+ *
+ * It's valid to return a different list size than the page size, if it's easier for this data
+ * source. It is generally safer to increase number loaded than reduce.
+ *
+ * @param currentBeginIndex Load items before this index, starting with currentBeginIndex - 1.
+ * @param currentBeginItem Load items after this item, can be used for precise querying based
+ * on item contents.
+ * @param pageSize Suggested number of items to load.
+ * @return List of items, in descending order, starting at position currentBeginIndex - 1.
+ *
+ * @hide
+ */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@WorkerThread
@Nullable
@@ -124,4 +111,15 @@ public abstract class ContiguousDataSource extends DataSource loadBeforeImpl(int currentBeginIndex,
+ @NonNull Value currentBeginItem, int pageSize);
+
+ /**
+ * Get the key from either the position, or item. Position may not match passed item's position,
+ * if trying to query the key from a position that isn't yet loaded, so a fallback item must be
+ * used.
+ */
+ abstract Key getKey(int position, Value item);
}
diff --git a/android/arch/paging/ContiguousDiffHelper.java b/android/arch/paging/ContiguousDiffHelper.java
new file mode 100644
index 00000000..7dd194b2
--- /dev/null
+++ b/android/arch/paging/ContiguousDiffHelper.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 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 android.arch.paging;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v7.recyclerview.extensions.DiffCallback;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.util.ListUpdateCallback;
+
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class ContiguousDiffHelper {
+ private ContiguousDiffHelper() {
+ }
+
+ @NonNull
+ static DiffUtil.DiffResult computeDiff(
+ final NullPaddedList oldList, final NullPaddedList newList,
+ final DiffCallback diffCallback, boolean detectMoves) {
+
+ if (!oldList.isImmutable()) {
+ throw new IllegalArgumentException("list must be immutable to safely perform diff");
+ }
+ if (!newList.isImmutable()) {
+ throw new IllegalArgumentException("list must be immutable to safely perform diff");
+ }
+ return DiffUtil.calculateDiff(new DiffUtil.Callback() {
+ @Nullable
+ @Override
+ public Object getChangePayload(int oldItemPosition, int newItemPosition) {
+ T oldItem = oldList.mList.get(oldItemPosition);
+ T newItem = newList.mList.get(newItemPosition);
+ if (oldItem == null || newItem == null) {
+ return null;
+ }
+ return diffCallback.getChangePayload(oldItem, newItem);
+ }
+
+ @Override
+ public int getOldListSize() {
+ return oldList.mList.size();
+ }
+
+ @Override
+ public int getNewListSize() {
+ return newList.mList.size();
+ }
+
+ @Override
+ public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
+ T oldItem = oldList.mList.get(oldItemPosition);
+ T newItem = newList.mList.get(newItemPosition);
+ if (oldItem == newItem) {
+ return true;
+ }
+ if (oldItem == null || newItem == null) {
+ return false;
+ }
+ return diffCallback.areItemsTheSame(oldItem, newItem);
+ }
+
+ @Override
+ public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
+ T oldItem = oldList.mList.get(oldItemPosition);
+ T newItem = newList.mList.get(newItemPosition);
+ if (oldItem == newItem) {
+ return true;
+ }
+ if (oldItem == null || newItem == null) {
+ return false;
+ }
+
+ return diffCallback.areContentsTheSame(oldItem, newItem);
+ }
+ }, detectMoves);
+ }
+
+ private static class OffsettingListUpdateCallback implements ListUpdateCallback {
+ private final int mOffset;
+ private final ListUpdateCallback mCallback;
+
+ private OffsettingListUpdateCallback(int offset, ListUpdateCallback callback) {
+ mOffset = offset;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onInserted(int position, int count) {
+ mCallback.onInserted(position + mOffset, count);
+ }
+
+ @Override
+ public void onRemoved(int position, int count) {
+ mCallback.onRemoved(position + mOffset, count);
+ }
+
+ @Override
+ public void onMoved(int fromPosition, int toPosition) {
+ mCallback.onRemoved(fromPosition + mOffset, toPosition + mOffset);
+ }
+
+ @Override
+ public void onChanged(int position, int count, Object payload) {
+ mCallback.onChanged(position + mOffset, count, payload);
+ }
+ }
+
+ /**
+ * TODO: improve diffing logic
+ *
+ * This function currently does a naive diff, assuming null does not become an item, and vice
+ * versa (so it won't dispatch onChange events for these). It's similar to passing a list with
+ * leading/trailing nulls in the beginning / end to DiffUtil, but dispatches the remove/insert
+ * for changed nulls at the beginning / end of the list.
+ *
+ * Note: if lists mutate between diffing the snapshot and dispatching the diff here, then we
+ * handle this by passing the snapshot to the callback, and dispatching those changes
+ * immediately after dispatching this diff.
+ */
+ static void dispatchDiff(ListUpdateCallback callback,
+ final NullPaddedList oldList, final NullPaddedList newList,
+ final DiffUtil.DiffResult diffResult) {
+
+ if (oldList.getLeadingNullCount() == 0
+ && oldList.getTrailingNullCount() == 0
+ && newList.getLeadingNullCount() == 0
+ && newList.getTrailingNullCount() == 0) {
+ // Simple case, dispatch & return
+ diffResult.dispatchUpdatesTo(callback);
+ return;
+ }
+
+ // First, remove or insert trailing nulls
+ final int trailingOld = oldList.getTrailingNullCount();
+ final int trailingNew = newList.getTrailingNullCount();
+ if (trailingOld > trailingNew) {
+ int count = trailingOld - trailingNew;
+ callback.onRemoved(oldList.size() - count, count);
+ } else if (trailingOld < trailingNew) {
+ callback.onInserted(oldList.size(), trailingNew - trailingOld);
+ }
+
+ // Second, remove or insert leading nulls
+ final int leadingOld = oldList.getLeadingNullCount();
+ final int leadingNew = newList.getLeadingNullCount();
+ if (leadingOld > leadingNew) {
+ callback.onRemoved(0, leadingOld - leadingNew);
+ } else if (leadingOld < leadingNew) {
+ callback.onInserted(0, leadingNew - leadingOld);
+ }
+
+ // apply the diff, with an offset if needed
+ if (leadingNew != 0) {
+ diffResult.dispatchUpdatesTo(new OffsettingListUpdateCallback(leadingNew, callback));
+ } else {
+ diffResult.dispatchUpdatesTo(callback);
+ }
+ }
+}
diff --git a/android/arch/paging/ContiguousDiffHelperTest.java b/android/arch/paging/ContiguousDiffHelperTest.java
new file mode 100644
index 00000000..4f221b34
--- /dev/null
+++ b/android/arch/paging/ContiguousDiffHelperTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 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 android.arch.paging;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.support.annotation.NonNull;
+import android.support.test.filters.SmallTest;
+import android.support.v7.recyclerview.extensions.DiffCallback;
+import android.support.v7.util.DiffUtil;
+import android.support.v7.util.ListUpdateCallback;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class ContiguousDiffHelperTest {
+ private interface CallbackValidator {
+ void validate(ListUpdateCallback callback);
+ }
+
+ private static final DiffCallback DIFF_CALLBACK = new DiffCallback() {
+ @Override
+ public boolean areItemsTheSame(@NonNull String oldItem, @NonNull String newItem) {
+ // first char means same item
+ return oldItem.charAt(0) == newItem.charAt(0);
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull String oldItem, @NonNull String newItem) {
+ return oldItem.equals(newItem);
+ }
+ };
+
+ private void validateTwoListDiff(StringPagedList oldList, StringPagedList newList,
+ CallbackValidator callbackValidator) {
+ DiffUtil.DiffResult diffResult = ContiguousDiffHelper.computeDiff(oldList, newList,
+ DIFF_CALLBACK, false);
+
+ ListUpdateCallback listUpdateCallback = Mockito.mock(ListUpdateCallback.class);
+ ContiguousDiffHelper.dispatchDiff(listUpdateCallback, oldList, newList, diffResult);
+
+ callbackValidator.validate(listUpdateCallback);
+ }
+
+ @Test
+ public void sameListNoUpdates() {
+ validateTwoListDiff(
+ new StringPagedList(5, 5, "a", "b", "c"),
+ new StringPagedList(5, 5, "a", "b", "c"),
+ new CallbackValidator() {
+ @Override
+ public void validate(ListUpdateCallback callback) {
+ verifyZeroInteractions(callback);
+ }
+ }
+ );
+ }
+
+ @Test
+ public void appendFill() {
+ validateTwoListDiff(
+ new StringPagedList(5, 5, "a", "b"),
+ new StringPagedList(5, 4, "a", "b", "c"),
+ new CallbackValidator() {
+ @Override
+ public void validate(ListUpdateCallback callback) {
+ verify(callback).onRemoved(11, 1);
+ verify(callback).onInserted(7, 1);
+ // NOTE: ideally would be onChanged(7, 1, null)
+ verifyNoMoreInteractions(callback);
+ }
+ }
+ );
+ }
+
+ @Test
+ public void prependFill() {
+ validateTwoListDiff(
+ new StringPagedList(5, 5, "b", "c"),
+ new StringPagedList(4, 5, "a", "b", "c"),
+ new CallbackValidator() {
+ @Override
+ public void validate(ListUpdateCallback callback) {
+ verify(callback).onRemoved(0, 1);
+ verify(callback).onInserted(4, 1);
+ //NOTE: ideally would be onChanged(4, 1, null);
+ verifyNoMoreInteractions(callback);
+ }
+ }
+ );
+ }
+
+ @Test
+ public void change() {
+ validateTwoListDiff(
+ new StringPagedList(5, 5, "a1", "b1", "c1"),
+ new StringPagedList(5, 5, "a2", "b1", "c2"),
+ new CallbackValidator() {
+ @Override
+ public void validate(ListUpdateCallback callback) {
+ verify(callback).onChanged(5, 1, null);
+ verify(callback).onChanged(7, 1, null);
+ verifyNoMoreInteractions(callback);
+ }
+ }
+ );
+ }
+}
diff --git a/android/arch/paging/ContiguousPagedList.java b/android/arch/paging/ContiguousPagedList.java
index 7835dbe3..d8907c3b 100644
--- a/android/arch/paging/ContiguousPagedList.java
+++ b/android/arch/paging/ContiguousPagedList.java
@@ -16,136 +16,101 @@
package android.arch.paging;
-import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.annotation.WorkerThread;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+class ContiguousPagedList extends NullPaddedList {
+
+ private final ContiguousDataSource, T> mDataSource;
+ private final Executor mMainThreadExecutor;
+ private final Executor mBackgroundThreadExecutor;
+ private final Config mConfig;
-class ContiguousPagedList extends PagedList implements PagedStorage.Callback {
- private final ContiguousDataSource mDataSource;
private boolean mPrependWorkerRunning = false;
private boolean mAppendWorkerRunning = false;
private int mPrependItemsRequested = 0;
private int mAppendItemsRequested = 0;
- @SuppressWarnings("unchecked")
- private final PagedStorage mKeyedStorage = (PagedStorage) mStorage;
-
- private final PageResult.Receiver mReceiver = new PageResult.Receiver() {
- @AnyThread
- @Override
- public void postOnPageResult(@NonNull final PageResult pageResult) {
- // NOTE: if we're already on main thread, this can delay page receive by a frame
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- onPageResult(pageResult);
- }
- });
- }
+ private int mLastLoad = 0;
+ private T mLastItem = null;
- @MainThread
- @Override
- public void onPageResult(@NonNull PageResult pageResult) {
- if (pageResult.page == null) {
- detach();
- return;
- }
+ private AtomicBoolean mDetached = new AtomicBoolean(false);
- if (isDetached()) {
- // No op, have detached
- return;
- }
+ private ArrayList> mCallbacks = new ArrayList<>();
- Page page = pageResult.page;
- if (pageResult.type == PageResult.INIT) {
- mKeyedStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
- pageResult.positionOffset, ContiguousPagedList.this);
- notifyInserted(0, mKeyedStorage.size());
- } else if (pageResult.type == PageResult.APPEND) {
- mKeyedStorage.appendPage(page, ContiguousPagedList.this);
- } else if (pageResult.type == PageResult.PREPEND) {
- mKeyedStorage.prependPage(page, ContiguousPagedList.this);
- }
- }
- };
-
- ContiguousPagedList(
- @NonNull ContiguousDataSource dataSource,
+ @WorkerThread
+ ContiguousPagedList(@NonNull ContiguousDataSource dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
- @NonNull Config config,
- final @Nullable K key) {
- super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor, config);
- mDataSource = dataSource;
-
- // blocking init just triggers the initial load on the construction thread -
- // Could still be posted with callback, if desired.
- mDataSource.loadInitial(key,
- mConfig.mInitialLoadSizeHint,
- mConfig.mEnablePlaceholders,
- mReceiver);
- }
-
- @MainThread
- @Override
- void dispatchUpdatesSinceSnapshot(
- @NonNull PagedList pagedListSnapshot, @NonNull Callback callback) {
-
- final PagedStorage, V> snapshot = pagedListSnapshot.mStorage;
-
- final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
- final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
-
- final int previousTrailing = snapshot.getTrailingNullCount();
- final int previousLeading = snapshot.getLeadingNullCount();
-
- // Validate that the snapshot looks like a previous version of this list - if it's not,
- // we can't be sure we'll dispatch callbacks safely
- if (newlyAppended < 0
- || newlyPrepended < 0
- || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
- || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
- || (mStorage.getStorageCount()
- != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
- throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
- + " to be a snapshot of this PagedList");
- }
-
- if (newlyAppended != 0) {
- final int changedCount = Math.min(previousTrailing, newlyAppended);
- final int addedCount = newlyAppended - changedCount;
+ Config config,
+ @Nullable K key) {
+ super();
- final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
- if (changedCount != 0) {
- callback.onChanged(endPosition, changedCount);
+ mDataSource = dataSource;
+ mMainThreadExecutor = mainThreadExecutor;
+ mBackgroundThreadExecutor = backgroundThreadExecutor;
+ mConfig = config;
+ NullPaddedList initialState = dataSource.loadInitial(
+ key, config.mInitialLoadSizeHint, config.mEnablePlaceholders);
+
+ if (initialState != null) {
+ mPositionOffset = initialState.getPositionOffset();
+
+ mLeadingNullCount = initialState.getLeadingNullCount();
+ mList = new ArrayList<>(initialState.mList);
+ mTrailingNullCount = initialState.getTrailingNullCount();
+
+ if (initialState.getLeadingNullCount() == 0
+ && initialState.getTrailingNullCount() == 0
+ && config.mPrefetchDistance < 1) {
+ throw new IllegalArgumentException("Null padding is required to support the 0"
+ + " prefetch case - require either null items or prefetching to fetch"
+ + " beyond initial load.");
}
- if (addedCount != 0) {
- callback.onInserted(endPosition + changedCount, addedCount);
+
+ if (initialState.size() != 0) {
+ mLastLoad = mLeadingNullCount + mList.size() / 2;
+ mLastItem = mList.get(mList.size() / 2);
}
+ } else {
+ mList = new ArrayList<>();
+ detach();
+ }
+ if (mList.size() == 0) {
+ // Empty initial state, so don't try and fetch data.
+ mPrependWorkerRunning = true;
+ mAppendWorkerRunning = true;
}
- if (newlyPrepended != 0) {
- final int changedCount = Math.min(previousLeading, newlyPrepended);
- final int addedCount = newlyPrepended - changedCount;
+ }
- if (changedCount != 0) {
- callback.onChanged(previousLeading, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(0, addedCount);
- }
+ @Override
+ public T get(int index) {
+ T item = super.get(index);
+ if (item != null) {
+ mLastItem = item;
}
+ return item;
}
- @MainThread
@Override
- protected void loadAroundInternal(int index) {
- int prependItems = mConfig.mPrefetchDistance - (index - mStorage.getLeadingNullCount());
- int appendItems = index + mConfig.mPrefetchDistance
- - (mStorage.getLeadingNullCount() + mStorage.getStorageCount());
+ public void loadAround(int index) {
+ mLastLoad = index + mPositionOffset;
+
+ int prependItems = mConfig.mPrefetchDistance - (index - mLeadingNullCount);
+ int appendItems = index + mConfig.mPrefetchDistance - (mLeadingNullCount + mList.size());
mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
if (mPrependItemsRequested > 0) {
@@ -158,6 +123,21 @@ class ContiguousPagedList