Merge "Deliver package broadcasts only to related profiles."
diff --git a/Android.mk b/Android.mk
index 232f5bf..966c64d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -76,6 +76,8 @@
core/java/android/app/ISearchManagerCallback.aidl \
core/java/android/app/IServiceConnection.aidl \
core/java/android/app/IStopUserCallback.aidl \
+ core/java/android/app/task/ITaskCallback.aidl \
+ core/java/android/app/task/ITaskService.aidl \
core/java/android/app/IThumbnailReceiver.aidl \
core/java/android/app/IThumbnailRetriever.aidl \
core/java/android/app/ITransientNotification.aidl \
diff --git a/api/current.txt b/api/current.txt
index 0794685..f6d976e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -598,6 +598,7 @@
field public static final int headerBackground = 16843055; // 0x101012f
field public static final int headerDividersEnabled = 16843310; // 0x101022e
field public static final int height = 16843093; // 0x1010155
+ field public static final int hideOnContentScroll = 16843855; // 0x101044f
field public static final int hint = 16843088; // 0x1010150
field public static final int homeAsUpIndicator = 16843531; // 0x101030b
field public static final int homeLayout = 16843549; // 0x101031d
@@ -3064,6 +3065,7 @@
method public abstract android.view.View getCustomView();
method public abstract int getDisplayOptions();
method public abstract int getHeight();
+ method public int getHideOffset();
method public abstract deprecated int getNavigationItemCount();
method public abstract deprecated int getNavigationMode();
method public abstract deprecated int getSelectedNavigationIndex();
@@ -3074,6 +3076,7 @@
method public android.content.Context getThemedContext();
method public abstract java.lang.CharSequence getTitle();
method public abstract void hide();
+ method public boolean isHideOnContentScrollEnabled();
method public abstract boolean isShowing();
method public abstract deprecated android.app.ActionBar.Tab newTab();
method public abstract deprecated void removeAllTabs();
@@ -3092,6 +3095,8 @@
method public abstract void setDisplayShowHomeEnabled(boolean);
method public abstract void setDisplayShowTitleEnabled(boolean);
method public abstract void setDisplayUseLogoEnabled(boolean);
+ method public void setHideOffset(int);
+ method public void setHideOnContentScrollEnabled(boolean);
method public void setHomeActionContentDescription(java.lang.CharSequence);
method public void setHomeActionContentDescription(int);
method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable);
@@ -5166,6 +5171,27 @@
}
+package android.app.task {
+
+ public class TaskParams implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.os.Bundle getExtras();
+ method public int getTaskId();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public abstract class TaskService extends android.app.Service {
+ ctor public TaskService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onStartTask(android.app.task.TaskParams);
+ method public abstract boolean onStopTask(android.app.task.TaskParams);
+ method public final void taskFinished(android.app.task.TaskParams, boolean);
+ field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_TASK_SERVICE";
+ }
+
+}
+
package android.appwidget {
public class AppWidgetHost {
@@ -7528,6 +7554,55 @@
method public abstract void onStatusChanged(int);
}
+ public class Task implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getBackoffPolicy();
+ method public android.os.Bundle getExtras();
+ method public long getInitialBackoffMillis();
+ method public long getIntervalMillis();
+ method public long getMaxExecutionDelayMillis();
+ method public long getMinLatencyMillis();
+ method public int getNetworkCapabilities();
+ method public java.lang.String getServiceClassName();
+ method public int getTaskId();
+ method public boolean isPeriodic();
+ method public boolean isRequireCharging();
+ method public boolean isRequireDeviceIdle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public static abstract interface Task.BackoffPolicy {
+ field public static final int EXPONENTIAL = 1; // 0x1
+ field public static final int LINEAR = 0; // 0x0
+ }
+
+ public final class Task.Builder {
+ ctor public Task.Builder(int, java.lang.Class<android.app.task.TaskService>);
+ method public android.content.Task build();
+ method public android.content.Task.Builder setBackoffCriteria(long, int);
+ method public android.content.Task.Builder setExtras(android.os.Bundle);
+ method public android.content.Task.Builder setMinimumLatency(long);
+ method public android.content.Task.Builder setOverrideDeadline(long);
+ method public android.content.Task.Builder setPeriodic(long);
+ method public android.content.Task.Builder setRequiredNetworkCapabilities(int);
+ method public android.content.Task.Builder setRequiresCharging(boolean);
+ method public android.content.Task.Builder setRequiresDeviceIdle(boolean);
+ }
+
+ public static abstract interface Task.NetworkType {
+ field public static final int ANY = 0; // 0x0
+ field public static final int UNMETERED = 1; // 0x1
+ }
+
+ public abstract class TaskManager {
+ ctor public TaskManager();
+ method public abstract void cancel(int);
+ method public abstract void cancelAll();
+ method public abstract java.util.List<android.content.Task> getAllPendingTasks();
+ method public abstract int schedule(android.content.Task);
+ }
+
public class UriMatcher {
ctor public UriMatcher(int);
method public void addURI(java.lang.String, java.lang.String, int);
@@ -30115,7 +30190,7 @@
method public boolean dispatchKeyEvent(android.view.KeyEvent);
method public boolean dispatchKeyEventPreIme(android.view.KeyEvent);
method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent);
- method public boolean dispatchNestedFling(float, float);
+ method public boolean dispatchNestedFling(float, float, boolean);
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -30908,7 +30983,7 @@
method public boolean onInterceptHoverEvent(android.view.MotionEvent);
method public boolean onInterceptTouchEvent(android.view.MotionEvent);
method protected abstract void onLayout(boolean, int, int, int, int);
- method public boolean onNestedFling(android.view.View, float, float);
+ method public boolean onNestedFling(android.view.View, float, float, boolean);
method public void onNestedPreScroll(android.view.View, int, int, int[]);
method public void onNestedScroll(android.view.View, int, int, int, int);
method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
@@ -31046,7 +31121,7 @@
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
- method public abstract boolean onNestedFling(android.view.View, float, float);
+ method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
method public abstract void onNestedPreScroll(android.view.View, int, int, int[]);
method public abstract void onNestedScroll(android.view.View, int, int, int, int);
method public abstract void onNestedScrollAccepted(android.view.View, android.view.View, int);
@@ -33108,7 +33183,8 @@
method public void onReceivedSslError(android.webkit.WebView, android.webkit.SslErrorHandler, android.net.http.SslError);
method public void onScaleChanged(android.webkit.WebView, float, float);
method public deprecated void onTooManyRedirects(android.webkit.WebView, android.os.Message, android.os.Message);
- method public void onUnhandledKeyEvent(android.webkit.WebView, android.view.KeyEvent);
+ method public void onUnhandledInputEvent(android.webkit.WebView, android.view.InputEvent);
+ method public deprecated void onUnhandledKeyEvent(android.webkit.WebView, android.view.KeyEvent);
method public android.webkit.WebResourceResponse shouldInterceptRequest(android.webkit.WebView, java.lang.String);
method public boolean shouldOverrideKeyEvent(android.webkit.WebView, android.view.KeyEvent);
method public boolean shouldOverrideUrlLoading(android.webkit.WebView, java.lang.String);
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 04f62e3..3c3df01 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -932,6 +932,66 @@
*/
public void setHomeActionContentDescription(int resId) { }
+ /**
+ * Enable hiding the action bar on content scroll.
+ *
+ * <p>If enabled, the action bar will scroll out of sight along with a
+ * {@link View#setNestedScrollingEnabled(boolean) nested scrolling child} view's content.
+ * The action bar must be in {@link Window#FEATURE_ACTION_BAR_OVERLAY overlay mode}
+ * to enable hiding on content scroll.</p>
+ *
+ * <p>When partially scrolled off screen the action bar is considered
+ * {@link #hide() hidden}. A call to {@link #show() show} will cause it to return to full view.
+ * </p>
+ * @param hideOnContentScroll true to enable hiding on content scroll.
+ */
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll) {
+ throw new UnsupportedOperationException("Hide on content scroll is not supported in " +
+ "this action bar configuration.");
+ }
+ }
+
+ /**
+ * Return whether the action bar is configured to scroll out of sight along with
+ * a {@link View#setNestedScrollingEnabled(boolean) nested scrolling child}.
+ *
+ * @return true if hide-on-content-scroll is enabled
+ * @see #setHideOnContentScrollEnabled(boolean)
+ */
+ public boolean isHideOnContentScrollEnabled() {
+ return false;
+ }
+
+ /**
+ * Return the current vertical offset of the action bar.
+ *
+ * <p>The action bar's current hide offset is the distance that the action bar is currently
+ * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+ * current measured {@link #getHeight() height} (fully invisible).</p>
+ *
+ * @return The action bar's offset toward its fully hidden state in pixels
+ */
+ public int getHideOffset() {
+ return 0;
+ }
+
+ /**
+ * Set the current hide offset of the action bar.
+ *
+ * <p>The action bar's current hide offset is the distance that the action bar is currently
+ * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+ * current measured {@link #getHeight() height} (fully invisible).</p>
+ *
+ * @param offset The action bar's offset toward its fully hidden state in pixels.
+ */
+ public void setHideOffset(int offset) {
+ if (offset != 0) {
+ throw new UnsupportedOperationException("Setting an explicit action bar hide offset " +
+ "is not supported in this action bar configuration.");
+ }
+ }
+
/** @hide */
public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index ad4027d..b917263 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -58,6 +58,8 @@
ZenModeConfig getZenModeConfig();
boolean setZenModeConfig(in ZenModeConfig config);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
- oneway void requestZenModeConditions(in IConditionListener callback, boolean requested);
+ oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
oneway void setZenModeCondition(in Uri conditionId);
+ oneway void setAutomaticZenModeConditions(in Uri[] conditionIds);
+ Condition[] getAutomaticZenModeConditions();
}
\ No newline at end of file
diff --git a/core/java/android/app/task/ITaskCallback.aidl b/core/java/android/app/task/ITaskCallback.aidl
new file mode 100644
index 0000000..ffa57d1
--- /dev/null
+++ b/core/java/android/app/task/ITaskCallback.aidl
@@ -0,0 +1,53 @@
+/**
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.task;
+
+import android.app.task.ITaskService;
+import android.app.task.TaskParams;
+
+/**
+ * The server side of the TaskManager IPC protocols. The app-side implementation
+ * invokes on this interface to indicate completion of the (asynchronous) instructions
+ * issued by the server.
+ *
+ * In all cases, the 'who' parameter is the caller's service binder, used to track
+ * which Task Service instance is reporting.
+ *
+ * {@hide}
+ */
+interface ITaskCallback {
+ /**
+ * Immediate callback to the system after sending a start signal, used to quickly detect ANR.
+ *
+ * @param taskId Unique integer used to identify this task.
+ */
+ void acknowledgeStartMessage(int taskId);
+ /**
+ * Immediate callback to the system after sending a stop signal, used to quickly detect ANR.
+ *
+ * @param taskId Unique integer used to identify this task.
+ */
+ void acknowledgeStopMessage(int taskId);
+ /*
+ * Tell the task manager that the client is done with its execution, so that it can go on to
+ * the next one and stop attributing wakelock time to us etc.
+ *
+ * @param taskId Unique integer used to identify this task.
+ * @param reschedule Whether or not to reschedule this task.
+ */
+ void taskFinished(int taskId, boolean reschedule);
+}
diff --git a/core/java/android/app/task/ITaskService.aidl b/core/java/android/app/task/ITaskService.aidl
new file mode 100644
index 0000000..87b0191
--- /dev/null
+++ b/core/java/android/app/task/ITaskService.aidl
@@ -0,0 +1,35 @@
+/**
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.task;
+
+import android.app.task.ITaskCallback;
+import android.app.task.TaskParams;
+
+import android.os.Bundle;
+
+/**
+ * Interface that the framework uses to communicate with application code that implements a
+ * TaskService. End user code does not implement this interface directly; instead, the app's
+ * service implementation will extend android.app.task.TaskService.
+ * {@hide}
+ */
+oneway interface ITaskService {
+ /** Begin execution of application's task. */
+ void startTask(in TaskParams taskParams);
+ /** Stop execution of application's task. */
+ void stopTask(in TaskParams taskParams);
+}
diff --git a/core/java/android/app/task/TaskParams.aidl b/core/java/android/app/task/TaskParams.aidl
new file mode 100644
index 0000000..9b25855
--- /dev/null
+++ b/core/java/android/app/task/TaskParams.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.task;
+
+parcelable TaskParams;
\ No newline at end of file
diff --git a/core/java/android/app/task/TaskParams.java b/core/java/android/app/task/TaskParams.java
new file mode 100644
index 0000000..429281c
--- /dev/null
+++ b/core/java/android/app/task/TaskParams.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app.task;
+
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains the parameters used to configure/identify your task. You do not create this object
+ * yourself, instead it is handed in to your application by the System.
+ */
+public class TaskParams implements Parcelable {
+
+ private final int taskId;
+ private final Bundle extras;
+ private final IBinder mCallback;
+
+ /**
+ * @return The unique id of this task, specified at creation time using
+ * {@link android.content.Task.Builder#Builder(int, Class)}.
+ */
+ public int getTaskId() {
+ return taskId;
+ }
+
+ /**
+ * @return The extras you passed in when constructing this task with
+ * {@link android.content.Task.Builder#setExtras(android.os.Bundle)}. This will
+ * never be null. If you did not set any extras this will be an empty bundle.
+ */
+ public Bundle getExtras() {
+ return extras;
+ }
+
+ /**
+ * @hide
+ */
+ public ITaskCallback getCallback() {
+ return ITaskCallback.Stub.asInterface(mCallback);
+ }
+
+ private TaskParams(Parcel in) {
+ taskId = in.readInt();
+ extras = in.readBundle();
+ mCallback = in.readStrongBinder();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(taskId);
+ dest.writeBundle(extras);
+ dest.writeStrongBinder(mCallback);
+ }
+
+ public static final Creator<TaskParams> CREATOR = new Creator<TaskParams>() {
+ @Override
+ public TaskParams createFromParcel(Parcel in) {
+ return new TaskParams(in);
+ }
+
+ @Override
+ public TaskParams[] newArray(int size) {
+ return new TaskParams[size];
+ }
+ };
+}
diff --git a/core/java/android/app/task/TaskService.java b/core/java/android/app/task/TaskService.java
new file mode 100644
index 0000000..81333be
--- /dev/null
+++ b/core/java/android/app/task/TaskService.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app.task;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Bundle;
+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;
+
+/**
+ * <p>Entry point for the callback from the {@link android.content.TaskManager}.</p>
+ * <p>This is the base class that handles asynchronous requests that were previously scheduled. You
+ * are responsible for overriding {@link TaskService#onStartTask(TaskParams)}, which is where
+ * you will implement your task logic.</p>
+ * <p>This service executes each incoming task on a {@link android.os.Handler} running on your
+ * application's main thread. This means that you <b>must</b> offload your execution logic to
+ * another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result
+ * in blocking any future callbacks from the TaskManager - specifically
+ * {@link #onStopTask(android.app.task.TaskParams)}, which is meant to inform you that the
+ * scheduling requirements are no longer being met.</p>
+ */
+public abstract class TaskService extends Service {
+ private static final String TAG = "TaskService";
+
+ /**
+ * Task services must be protected with this permission:
+ *
+ * <pre class="prettyprint">
+ * <service android:name="MyTaskService"
+ * android:permission="android.permission.BIND_TASK_SERVICE" >
+ * ...
+ * </service>
+ * </pre>
+ *
+ * <p>If a task service is declared in the manifest but not protected with this
+ * permission, that service will be ignored by the OS.
+ */
+ public static final String PERMISSION_BIND =
+ "android.permission.BIND_TASK_SERVICE";
+
+ /**
+ * Identifier for a message that will result in a call to
+ * {@link #onStartTask(android.app.task.TaskParams)}.
+ */
+ private final int MSG_EXECUTE_TASK = 0;
+ /**
+ * Message that will result in a call to {@link #onStopTask(android.app.task.TaskParams)}.
+ */
+ private final int MSG_STOP_TASK = 1;
+ /**
+ * Message that the client has completed execution of this task.
+ */
+ private final int MSG_TASK_FINISHED = 2;
+
+ /** Lock object for {@link #mHandler}. */
+ private final Object mHandlerLock = new Object();
+
+ /**
+ * Handler we post tasks to. Responsible for calling into the client logic, and handling the
+ * callback to the system.
+ */
+ @GuardedBy("mHandlerLock")
+ TaskHandler mHandler;
+
+ /** Binder for this service. */
+ ITaskService mBinder = new ITaskService.Stub() {
+ @Override
+ public void startTask(TaskParams taskParams) {
+ ensureHandler();
+ Message m = Message.obtain(mHandler, MSG_EXECUTE_TASK, taskParams);
+ m.sendToTarget();
+ }
+ @Override
+ public void stopTask(TaskParams taskParams) {
+ ensureHandler();
+ Message m = Message.obtain(mHandler, MSG_STOP_TASK, taskParams);
+ m.sendToTarget();
+ }
+ };
+
+ /** @hide */
+ void ensureHandler() {
+ synchronized (mHandlerLock) {
+ if (mHandler == null) {
+ mHandler = new TaskHandler(getMainLooper());
+ }
+ }
+ }
+
+ /**
+ * Runs on application's main thread - callbacks are meant to offboard work to some other
+ * (app-specified) mechanism.
+ * @hide
+ */
+ class TaskHandler extends Handler {
+ TaskHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final TaskParams params = (TaskParams) msg.obj;
+ switch (msg.what) {
+ case MSG_EXECUTE_TASK:
+ try {
+ TaskService.this.onStartTask(params);
+ } catch (Exception e) {
+ Log.e(TAG, "Error while executing task: " + params.getTaskId());
+ throw new RuntimeException(e);
+ } finally {
+ maybeAckMessageReceived(params, MSG_EXECUTE_TASK);
+ }
+ break;
+ case MSG_STOP_TASK:
+ try {
+ TaskService.this.onStopTask(params);
+ } catch (Exception e) {
+ Log.e(TAG, "Application unable to handle onStopTask.", e);
+ throw new RuntimeException(e);
+ } finally {
+ maybeAckMessageReceived(params, MSG_STOP_TASK);
+ }
+ break;
+ case MSG_TASK_FINISHED:
+ final boolean needsReschedule = (msg.arg2 == 1);
+ ITaskCallback callback = params.getCallback();
+ if (callback != null) {
+ try {
+ callback.taskFinished(params.getTaskId(), needsReschedule);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error reporting task finish to system: binder has gone" +
+ "away.");
+ }
+ } else {
+ Log.e(TAG, "finishTask() called for a nonexistent task id.");
+ }
+ break;
+ default:
+ Log.e(TAG, "Unrecognised message received.");
+ break;
+ }
+ }
+
+ /**
+ * Messages come in on the application's main thread, so rather than run the risk of
+ * waiting for an app that may be doing something foolhardy, we ack to the system after
+ * processing a message. This allows us to throw up an ANR dialogue as quickly as possible.
+ * @param params id of the task we're acking.
+ * @param state Information about what message we're acking.
+ */
+ private void maybeAckMessageReceived(TaskParams params, int state) {
+ final ITaskCallback callback = params.getCallback();
+ final int taskId = params.getTaskId();
+ if (callback != null) {
+ try {
+ if (state == MSG_EXECUTE_TASK) {
+ callback.acknowledgeStartMessage(taskId);
+ } else if (state == MSG_STOP_TASK) {
+ callback.acknowledgeStopMessage(taskId);
+ }
+ } catch(RemoteException e) {
+ Log.e(TAG, "System unreachable for starting task.");
+ }
+ } else {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, state + ": Attempting to ack a task that has already been" +
+ "processed.");
+ }
+ }
+ }
+ }
+
+ /** @hide */
+ public final IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
+
+ /**
+ * Override this method with the callback logic for your task. 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 task, including the extras bundle you
+ * optionally provided at task-creation time.
+ */
+ public abstract void onStartTask(TaskParams params);
+
+ /**
+ * This method is called if your task should be stopped even before you've called
+ * {@link #taskFinished(TaskParams, boolean)}.
+ *
+ * <p>This will happen if the requirements specified at schedule time are no longer met. For
+ * example you may have requested WiFi with
+ * {@link android.content.Task.Builder#setRequiredNetworkCapabilities(int)}, yet while your
+ * task was executing the user toggled WiFi. Another example is if you had specified
+ * {@link android.content.Task.Builder#setRequiresDeviceIdle(boolean)}, and the phone left its
+ * 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
+ * repercussion is that the system will cease to hold a wakelock for you.</p>
+ *
+ * <p>After you've done your clean-up you are still expected to call
+ * {@link #taskFinished(TaskParams, boolean)} this will inform the TaskManager that all is well, and
+ * allow you to reschedule your task as it is probably uncompleted. Until you call
+ * taskFinished() you will not receive any newly scheduled tasks with the given task id as the
+ * TaskManager will consider the task to be in an error state.</p>
+ *
+ * @param params Parameters specifying info about this task.
+ * @return True to indicate to the TaskManager whether you'd like to reschedule this task based
+ * on the criteria provided at task creation-time. False to drop the task. Regardless of the
+ * value returned, your task must stop executing.
+ */
+ public abstract boolean onStopTask(TaskParams params);
+
+ /**
+ * Callback to inform the TaskManager you have completed execution. 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.
+ * <p>
+ * You can specify post-execution behaviour to the scheduler here with <code>needsReschedule
+ * </code>. This will apply a back-off timer to your task based on the default, or what was
+ * set with {@link android.content.Task.Builder#setBackoffCriteria(long, int)}. The
+ * original requirements are always honoured even for a backed-off task.
+ * Note that a task running in idle mode will not be backed-off. Instead what will happen
+ * is the task will be re-added to the queue and re-executed within a future idle
+ * maintenance window.
+ * </p>
+ *
+ * @param params Parameters specifying system-provided info about this task, this was given to
+ * your application in {@link #onStartTask(TaskParams)}.
+ * @param needsReschedule True if this task is complete, false if you want the TaskManager to
+ * reschedule you.
+ */
+ public final void taskFinished(TaskParams params, boolean needsReschedule) {
+ ensureHandler();
+ Message m = Message.obtain(mHandler, MSG_TASK_FINISHED, params);
+ m.arg2 = needsReschedule ? 1 : 0;
+ m.sendToTarget();
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/content/Task.java b/core/java/android/content/Task.java
new file mode 100644
index 0000000..ed5ed884
--- /dev/null
+++ b/core/java/android/content/Task.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content;
+
+import android.app.task.TaskService;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Container of data passed to the {@link android.content.TaskManager} fully encapsulating the
+ * parameters required to schedule work against the calling application. These are constructed
+ * using the {@link Task.Builder}.
+ */
+public class Task implements Parcelable {
+
+ public interface NetworkType {
+ public final int ANY = 0;
+ public final int UNMETERED = 1;
+ }
+
+ /**
+ * Linear: retry_time(failure_time, t) = failure_time + initial_retry_delay * t, t >= 1
+ * Expon: retry_time(failure_time, t) = failure_time + initial_retry_delay ^ t, t >= 1
+ */
+ public interface BackoffPolicy {
+ public final int LINEAR = 0;
+ public final int EXPONENTIAL = 1;
+ }
+
+ /**
+ * Unique task id associated with this class. This is assigned to your task by the scheduler.
+ */
+ public int getTaskId() {
+ return taskId;
+ }
+
+ /**
+ * Bundle of extras which are returned to your application at execution time.
+ */
+ public Bundle getExtras() {
+ return extras;
+ }
+
+ /**
+ * Name of the service endpoint that will be called back into by the TaskManager.
+ */
+ public String getServiceClassName() {
+ return serviceClassName;
+ }
+
+ /**
+ * Whether this task needs the device to be plugged in.
+ */
+ public boolean isRequireCharging() {
+ return requireCharging;
+ }
+
+ /**
+ * Whether this task needs the device to be in an Idle maintenance window.
+ */
+ public boolean isRequireDeviceIdle() {
+ return requireDeviceIdle;
+ }
+
+ /**
+ * See {@link android.content.Task.NetworkType} for a description of this value.
+ */
+ public int getNetworkCapabilities() {
+ return networkCapabilities;
+ }
+
+ /**
+ * Set for a task that does not recur periodically, to specify a delay after which the task
+ * will be eligible for execution. This value is not set if the task recurs periodically.
+ */
+ public long getMinLatencyMillis() {
+ return minLatencyMillis;
+ }
+
+ /**
+ * See {@link Builder#setOverrideDeadline(long)}. This value is not set if the task recurs
+ * periodically.
+ */
+ public long getMaxExecutionDelayMillis() {
+ return maxExecutionDelayMillis;
+ }
+
+ /**
+ * Track whether this task will repeat with a given period.
+ */
+ public boolean isPeriodic() {
+ return isPeriodic;
+ }
+
+ /**
+ * Set to the interval between occurrences of this task. This value is <b>not</b> set if the
+ * task does not recur periodically.
+ */
+ public long getIntervalMillis() {
+ return intervalMillis;
+ }
+
+ /**
+ * The amount of time the TaskManager will wait before rescheduling a failed task. This value
+ * will be increased depending on the backoff policy specified at task creation time. Defaults
+ * to 5 seconds.
+ */
+ public long getInitialBackoffMillis() {
+ return initialBackoffMillis;
+ }
+
+ /**
+ * See {@link android.content.Task.BackoffPolicy} for an explanation of the values this field
+ * can take. This defaults to exponential.
+ */
+ public int getBackoffPolicy() {
+ return backoffPolicy;
+ }
+
+ private final int taskId;
+ // TODO: Change this to use PersistableBundle when that lands in master.
+ private final Bundle extras;
+ private final String serviceClassName;
+ private final boolean requireCharging;
+ private final boolean requireDeviceIdle;
+ private final int networkCapabilities;
+ private final long minLatencyMillis;
+ private final long maxExecutionDelayMillis;
+ private final boolean isPeriodic;
+ private final long intervalMillis;
+ private final long initialBackoffMillis;
+ private final int backoffPolicy;
+
+ private Task(Parcel in) {
+ taskId = in.readInt();
+ extras = in.readBundle();
+ serviceClassName = in.readString();
+ requireCharging = in.readInt() == 1;
+ requireDeviceIdle = in.readInt() == 1;
+ networkCapabilities = in.readInt();
+ minLatencyMillis = in.readLong();
+ maxExecutionDelayMillis = in.readLong();
+ isPeriodic = in.readInt() == 1;
+ intervalMillis = in.readLong();
+ initialBackoffMillis = in.readLong();
+ backoffPolicy = in.readInt();
+ }
+
+ private Task(Task.Builder b) {
+ taskId = b.mTaskId;
+ extras = new Bundle(b.mExtras);
+ serviceClassName = b.mTaskServiceClassName;
+ requireCharging = b.mRequiresCharging;
+ requireDeviceIdle = b.mRequiresDeviceIdle;
+ networkCapabilities = b.mNetworkCapabilities;
+ minLatencyMillis = b.mMinLatencyMillis;
+ maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
+ isPeriodic = b.mIsPeriodic;
+ intervalMillis = b.mIntervalMillis;
+ initialBackoffMillis = b.mInitialBackoffMillis;
+ backoffPolicy = b.mBackoffPolicy;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(taskId);
+ out.writeBundle(extras);
+ out.writeString(serviceClassName);
+ out.writeInt(requireCharging ? 1 : 0);
+ out.writeInt(requireDeviceIdle ? 1 : 0);
+ out.writeInt(networkCapabilities);
+ out.writeLong(minLatencyMillis);
+ out.writeLong(maxExecutionDelayMillis);
+ out.writeInt(isPeriodic ? 1 : 0);
+ out.writeLong(intervalMillis);
+ out.writeLong(initialBackoffMillis);
+ out.writeInt(backoffPolicy);
+ }
+
+ public static final Creator<Task> CREATOR = new Creator<Task>() {
+ @Override
+ public Task createFromParcel(Parcel in) {
+ return new Task(in);
+ }
+
+ @Override
+ public Task[] newArray(int size) {
+ return new Task[size];
+ }
+ };
+
+ /**
+ * Builder class for constructing {@link Task} objects.
+ */
+ public final class Builder {
+ private int mTaskId;
+ private Bundle mExtras;
+ private String mTaskServiceClassName;
+ // Requirements.
+ private boolean mRequiresCharging;
+ private boolean mRequiresDeviceIdle;
+ private int mNetworkCapabilities;
+ // One-off parameters.
+ private long mMinLatencyMillis;
+ private long mMaxExecutionDelayMillis;
+ // Periodic parameters.
+ private boolean mIsPeriodic;
+ private long mIntervalMillis;
+ // Back-off parameters.
+ private long mInitialBackoffMillis = 5000L;
+ private int mBackoffPolicy = BackoffPolicy.EXPONENTIAL;
+ /** Easy way to track whether the client has tried to set a back-off policy. */
+ private boolean mBackoffPolicySet = false;
+
+ /**
+ * @param taskId Application-provided id for this task. Subsequent calls to cancel, or
+ * tasks created with the same taskId, will update the pre-existing task with
+ * the same id.
+ * @param cls The endpoint that you implement that will receive the callback from the
+ * TaskManager.
+ */
+ public Builder(int taskId, Class<TaskService> cls) {
+ mTaskServiceClassName = cls.getClass().getName();
+ mTaskId = taskId;
+ }
+
+ /**
+ * Set optional extras. This is persisted, so we only allow primitive types.
+ * @param extras Bundle containing extras you want the scheduler to hold on to for you.
+ */
+ public Builder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Set some description of the kind of network capabilities you would like to have. This
+ * will be a parameter defined in {@link android.content.Task.NetworkType}.
+ * Not calling this function means the network is not necessary.
+ * Bear in mind that calling this function defines network as a strict requirement for your
+ * task if the network requested is not available your task will never run. See
+ * {@link #setOverrideDeadline(long)} to change this behaviour.
+ */
+ public Builder setRequiredNetworkCapabilities(int networkCapabilities) {
+ mNetworkCapabilities = networkCapabilities;
+ return this;
+ }
+
+ /*
+ * Specify that to run this task, the device needs to be plugged in. This defaults to
+ * false.
+ * @param requireCharging Whether or not the device is plugged in.
+ */
+ public Builder setRequiresCharging(boolean requiresCharging) {
+ mRequiresCharging = requiresCharging;
+ return this;
+ }
+
+ /**
+ * Specify that to run, the task needs the device to be in idle mode. This defaults to
+ * false.
+ * <p>Idle mode is a loose definition provided by the system, which means that the device
+ * is not in use, and has not been in use for some time. As such, it is a good time to
+ * perform resource heavy tasks. Bear in mind that battery usage will still be attributed
+ * to your application, and surfaced to the user in battery stats.</p>
+ * @param requiresDeviceIdle Whether or not the device need be within an idle maintenance
+ * window.
+ */
+ public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
+ mRequiresDeviceIdle = requiresDeviceIdle;
+ return this;
+ }
+
+ /**
+ * Specify that this task should recur with the provided interval, not more than once per
+ * period. You have no control over when within this interval this task will be executed,
+ * only the guarantee that it will be executed at most once within this interval.
+ * A periodic task will be repeated until the phone is turned off, however it will only be
+ * persisted if the client app has declared the
+ * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission. You can schedule
+ * periodic tasks without this permission, they simply will cease to exist after the phone
+ * restarts.
+ * Setting this function on the builder with {@link #setMinimumLatency(long)} or
+ * {@link #setOverrideDeadline(long)} will result in an error.
+ * @param intervalMillis Millisecond interval for which this task will repeat.
+ */
+ public Builder setPeriodic(long intervalMillis) {
+ mIsPeriodic = true;
+ mIntervalMillis = intervalMillis;
+ return this;
+ }
+
+ /**
+ * Specify that this task should be delayed by the provided amount of time.
+ * Because it doesn't make sense setting this property on a periodic task, doing so will
+ * throw an {@link java.lang.IllegalArgumentException} when
+ * {@link android.content.Task.Builder#build()} is called.
+ * @param minLatencyMillis Milliseconds before which this task will not be considered for
+ * execution.
+ */
+ public Builder setMinimumLatency(long minLatencyMillis) {
+ mMinLatencyMillis = minLatencyMillis;
+ return this;
+ }
+
+ /**
+ * Set deadline which is the maximum scheduling latency. The task will be run by this
+ * deadline even if other requirements are not met. Because it doesn't make sense setting
+ * this property on a periodic task, doing so will throw an
+ * {@link java.lang.IllegalArgumentException} when
+ * {@link android.content.Task.Builder#build()} is called.
+ */
+ public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
+ mMaxExecutionDelayMillis = maxExecutionDelayMillis;
+ return this;
+ }
+
+ /**
+ * Set up the back-off/retry policy.
+ * This defaults to some respectable values: {5 seconds, Exponential}. We cap back-off at
+ * 1hr.
+ * Note that trying to set a backoff criteria for a task with
+ * {@link #setRequiresDeviceIdle(boolean)} will throw an exception when you call build().
+ * This is because back-off typically does not make sense for these types of tasks. See
+ * {@link android.app.task.TaskService#taskFinished(android.app.task.TaskParams, boolean)}
+ * for more description of the return value for the case of a task executing while in idle
+ * mode.
+ * @param initialBackoffMillis Millisecond time interval to wait initially when task has
+ * failed.
+ * @param backoffPolicy is one of {@link BackoffPolicy}
+ */
+ public Builder setBackoffCriteria(long initialBackoffMillis, int backoffPolicy) {
+ mBackoffPolicySet = true;
+ mInitialBackoffMillis = initialBackoffMillis;
+ mBackoffPolicy = backoffPolicy;
+ return this;
+ }
+
+ /**
+ * @return The task object to hand to the TaskManager. This object is immutable.
+ */
+ public Task build() {
+ // Check that extras bundle only contains primitive types.
+ try {
+ for (String key : extras.keySet()) {
+ Object value = extras.get(key);
+ if (value == null) continue;
+ if (value instanceof Long) continue;
+ if (value instanceof Integer) continue;
+ if (value instanceof Boolean) continue;
+ if (value instanceof Float) continue;
+ if (value instanceof Double) continue;
+ if (value instanceof String) continue;
+ throw new IllegalArgumentException("Unexpected value type: "
+ + value.getClass().getName());
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (RuntimeException exc) {
+ throw new IllegalArgumentException("error unparcelling Bundle", exc);
+ }
+ // Check that a deadline was not set on a periodic task.
+ if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
+ throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
+ "periodic task.");
+ }
+ if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
+ throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
+ "periodic task");
+ }
+ if (mBackoffPolicySet && mRequiresDeviceIdle) {
+ throw new IllegalArgumentException("An idle mode task will not respect any" +
+ " back-off policy, so calling setBackoffCriteria with" +
+ " setRequiresDeviceIdle is an error.");
+ }
+ return new Task(this);
+ }
+ }
+
+}
diff --git a/core/java/android/content/TaskManager.java b/core/java/android/content/TaskManager.java
new file mode 100644
index 0000000..d28d78a
--- /dev/null
+++ b/core/java/android/content/TaskManager.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content;
+
+import java.util.List;
+
+/**
+ * Class for scheduling various types of tasks with the scheduling framework on the device.
+ *
+ * Get an instance of this class through {@link Context#getSystemService(String)}.
+ */
+public abstract class TaskManager {
+ /*
+ * Returned from {@link #schedule(Task)} when an invalid parameter was supplied. This can occur
+ * if the run-time for your task is too short, or perhaps the system can't resolve the
+ * requisite {@link TaskService} in your package.
+ */
+ static final int RESULT_INVALID_PARAMETERS = -1;
+ /**
+ * Returned from {@link #schedule(Task)} if this application has made too many requests for
+ * work over too short a time.
+ */
+ // TODO: Determine if this is necessary.
+ static final int RESULT_OVER_QUOTA = -2;
+
+ /*
+ * @param task The task you wish scheduled. See {@link Task#TaskBuilder} for more detail on
+ * the sorts of tasks you can schedule.
+ * @return If >0, this int corresponds to the taskId of the successfully scheduled task.
+ * Otherwise you have to compare the return value to the error codes defined in this class.
+ */
+ public abstract int schedule(Task task);
+
+ /**
+ * Cancel a task that is pending in the TaskManager.
+ * @param taskId unique identifier for this task. Obtain this value from the tasks returned by
+ * {@link #getAllPendingTasks()}.
+ * @return
+ */
+ public abstract void cancel(int taskId);
+
+ /**
+ * Cancel all tasks that have been registered with the TaskManager by this package.
+ */
+ public abstract void cancelAll();
+
+ /**
+ * @return a list of all the tasks registered by this package that have not yet been executed.
+ */
+ public abstract List<Task> getAllPendingTasks();
+
+}
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 033332c..bea8d1c 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -274,48 +274,6 @@
return PROXY_VALID;
}
- static class AndroidProxySelectorRoutePlanner
- extends org.apache.http.impl.conn.ProxySelectorRoutePlanner {
-
- private Context mContext;
-
- public AndroidProxySelectorRoutePlanner(SchemeRegistry schreg, ProxySelector prosel,
- Context context) {
- super(schreg, prosel);
- mContext = context;
- }
-
- @Override
- protected java.net.Proxy chooseProxy(List<java.net.Proxy> proxies, HttpHost target,
- HttpRequest request, HttpContext context) {
- return getProxy(mContext, target.getHostName());
- }
-
- @Override
- protected HttpHost determineProxy(HttpHost target, HttpRequest request,
- HttpContext context) {
- return getPreferredHttpHost(mContext, target.getHostName());
- }
-
- @Override
- public HttpRoute determineRoute(HttpHost target, HttpRequest request,
- HttpContext context) {
- HttpHost proxy = getPreferredHttpHost(mContext, target.getHostName());
- if (proxy == null) {
- return new HttpRoute(target);
- } else {
- return new HttpRoute(target, null, proxy, false);
- }
- }
- }
-
- /** @hide */
- public static final HttpRoutePlanner getAndroidProxySelectorRoutePlanner(Context context) {
- AndroidProxySelectorRoutePlanner ret = new AndroidProxySelectorRoutePlanner(
- new SchemeRegistry(), ProxySelector.getDefault(), context);
- return ret;
- }
-
/** @hide */
public static final void setHttpProxySystemProperty(ProxyProperties p) {
String host = null;
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 71e3166..dd8b34d 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -71,7 +71,7 @@
dest.writeParcelable(id, 0);
dest.writeString(caption);
dest.writeInt(state);
- dest.writeInt(flags);
+ dest.writeInt(this.flags);
}
@Override
@@ -92,6 +92,14 @@
throw new IllegalArgumentException("state is invalid: " + state);
}
+ public static String relevanceToString(int flags) {
+ final boolean now = (flags & FLAG_RELEVANT_NOW) != 0;
+ final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0;
+ if (!now && !always) return "NONE";
+ if (now && always) return "NOW, ALWAYS";
+ return now ? "NOW" : "ALWAYS";
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof Condition)) return false;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 925ddcf..846e292 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -16,6 +16,8 @@
package android.service.notification;
+import android.content.ComponentName;
+import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -25,6 +27,8 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -51,6 +55,10 @@
private static final String SLEEP_ATT_END_HR = "endHour";
private static final String SLEEP_ATT_END_MIN = "endMin";
+ private static final String CONDITION_TAG = "condition";
+ private static final String CONDITION_ATT_COMPONENT = "component";
+ private static final String CONDITION_ATT_ID = "id";
+
public boolean allowCalls;
public boolean allowMessages;
@@ -59,6 +67,8 @@
public int sleepStartMinute;
public int sleepEndHour;
public int sleepEndMinute;
+ public ComponentName[] conditionComponents;
+ public Uri[] conditionIds;
public ZenModeConfig() { }
@@ -72,6 +82,16 @@
sleepStartMinute = source.readInt();
sleepEndHour = source.readInt();
sleepEndMinute = source.readInt();
+ int len = source.readInt();
+ if (len > 0) {
+ conditionComponents = new ComponentName[len];
+ source.readTypedArray(conditionComponents, ComponentName.CREATOR);
+ }
+ len = source.readInt();
+ if (len > 0) {
+ conditionIds = new Uri[len];
+ source.readTypedArray(conditionIds, Uri.CREATOR);
+ }
}
@Override
@@ -88,6 +108,18 @@
dest.writeInt(sleepStartMinute);
dest.writeInt(sleepEndHour);
dest.writeInt(sleepEndMinute);
+ if (conditionComponents != null && conditionComponents.length > 0) {
+ dest.writeInt(conditionComponents.length);
+ dest.writeTypedArray(conditionComponents, 0);
+ } else {
+ dest.writeInt(0);
+ }
+ if (conditionIds != null && conditionIds.length > 0) {
+ dest.writeInt(conditionIds.length);
+ dest.writeTypedArray(conditionIds, 0);
+ } else {
+ dest.writeInt(0);
+ }
}
@Override
@@ -98,6 +130,10 @@
.append(",sleepMode=").append(sleepMode)
.append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
.append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
+ .append(",conditionComponents=")
+ .append(conditionComponents == null ? null : TextUtils.join(",", conditionComponents))
+ .append(",conditionIds=")
+ .append(conditionIds == null ? null : TextUtils.join(",", conditionIds))
.append(']').toString();
}
@@ -112,13 +148,16 @@
&& other.sleepStartHour == sleepStartHour
&& other.sleepStartMinute == sleepStartMinute
&& other.sleepEndHour == sleepEndHour
- && other.sleepEndMinute == sleepEndMinute;
+ && other.sleepEndMinute == sleepEndMinute
+ && Objects.deepEquals(other.conditionComponents, conditionComponents)
+ && Objects.deepEquals(other.conditionIds, conditionIds);
}
@Override
public int hashCode() {
return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour,
- sleepStartMinute, sleepEndHour, sleepEndMinute);
+ sleepStartMinute, sleepEndHour, sleepEndMinute,
+ Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds));
}
public boolean isValid() {
@@ -136,9 +175,18 @@
if (!ZEN_TAG.equals(tag)) return null;
final ZenModeConfig rt = new ZenModeConfig();
final int version = Integer.parseInt(parser.getAttributeValue(null, ZEN_ATT_VERSION));
+ final ArrayList<ComponentName> conditionComponents = new ArrayList<ComponentName>();
+ final ArrayList<Uri> conditionIds = new ArrayList<Uri>();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
- if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) return rt;
+ if (type == XmlPullParser.END_TAG && ZEN_TAG.equals(tag)) {
+ if (!conditionComponents.isEmpty()) {
+ rt.conditionComponents = conditionComponents
+ .toArray(new ComponentName[conditionComponents.size()]);
+ rt.conditionIds = conditionIds.toArray(new Uri[conditionIds.size()]);
+ }
+ return rt;
+ }
if (type == XmlPullParser.START_TAG) {
if (ALLOW_TAG.equals(tag)) {
rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
@@ -155,10 +203,18 @@
rt.sleepStartMinute = isValidMinute(startMinute) ? startMinute : 0;
rt.sleepEndHour = isValidHour(endHour) ? endHour : 0;
rt.sleepEndMinute = isValidMinute(endMinute) ? endMinute : 0;
+ } else if (CONDITION_TAG.equals(tag)) {
+ final ComponentName component =
+ safeComponentName(parser, CONDITION_ATT_COMPONENT);
+ final Uri conditionId = safeUri(parser, CONDITION_ATT_ID);
+ if (component != null && conditionId != null) {
+ conditionComponents.add(component);
+ conditionIds.add(conditionId);
+ }
}
}
}
- return rt;
+ throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
public void writeXml(XmlSerializer out) throws IOException {
@@ -180,6 +236,16 @@
out.attribute(null, SLEEP_ATT_END_MIN, Integer.toString(sleepEndMinute));
out.endTag(null, SLEEP_TAG);
+ if (conditionComponents != null && conditionIds != null
+ && conditionComponents.length == conditionIds.length) {
+ for (int i = 0; i < conditionComponents.length; i++) {
+ out.startTag(null, CONDITION_TAG);
+ out.attribute(null, CONDITION_ATT_COMPONENT,
+ conditionComponents[i].flattenToString());
+ out.attribute(null, CONDITION_ATT_ID, conditionIds[i].toString());
+ out.endTag(null, CONDITION_TAG);
+ }
+ }
out.endTag(null, ZEN_TAG);
}
@@ -203,6 +269,18 @@
return Integer.valueOf(val);
}
+ private static ComponentName safeComponentName(XmlPullParser parser, String att) {
+ final String val = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(val)) return null;
+ return ComponentName.unflattenFromString(val);
+ }
+
+ private static Uri safeUri(XmlPullParser parser, String att) {
+ final String val = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(val)) return null;
+ return Uri.parse(val);
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 85e3b3d..6afff4d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17989,7 +17989,9 @@
*
* <p>If this property is set to true the view will be permitted to initiate nested
* scrolling operations with a compatible parent view in the current hierarchy. If this
- * view does not implement nested scrolling this will have no effect.</p>
+ * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+ * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping}
+ * the nested scroll.</p>
*
* @param enabled true to enable nested scrolling, false to disable
*
@@ -17999,6 +18001,7 @@
if (enabled) {
mPrivateFlags3 |= PFLAG3_NESTED_SCROLLING_ENABLED;
} else {
+ stopNestedScroll();
mPrivateFlags3 &= ~PFLAG3_NESTED_SCROLLING_ENABLED;
}
}
@@ -18138,23 +18141,29 @@
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
- int startX = 0;
- int startY = 0;
- if (offsetInWindow != null) {
- getLocationInWindow(offsetInWindow);
- startX = offsetInWindow[0];
- startY = offsetInWindow[1];
- }
+ if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
+ }
- mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
- dxUnconsumed, dyUnconsumed);
+ mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
+ dxUnconsumed, dyUnconsumed);
- if (offsetInWindow != null) {
- getLocationInWindow(offsetInWindow);
- offsetInWindow[0] -= startX;
- offsetInWindow[1] -= startY;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return true;
+ } else if (offsetInWindow != null) {
+ // No motion, no dispatch. Keep offsetInWindow up to date.
+ offsetInWindow[0] = 0;
+ offsetInWindow[1] = 0;
}
- return true;
}
return false;
}
@@ -18180,30 +18189,35 @@
*/
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
- int startX = 0;
- int startY = 0;
- if (offsetInWindow != null) {
- getLocationInWindow(offsetInWindow);
- startX = offsetInWindow[0];
- startY = offsetInWindow[1];
- }
-
- if (consumed == null) {
- if (mTempNestedScrollConsumed == null) {
- mTempNestedScrollConsumed = new int[2];
+ if (dx != 0 || dy != 0) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
}
- consumed = mTempNestedScrollConsumed;
- }
- consumed[0] = 0;
- consumed[1] = 0;
- mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
- if (offsetInWindow != null) {
- getLocationInWindow(offsetInWindow);
- offsetInWindow[0] -= startX;
- offsetInWindow[1] -= startY;
+ if (consumed == null) {
+ if (mTempNestedScrollConsumed == null) {
+ mTempNestedScrollConsumed = new int[2];
+ }
+ consumed = mTempNestedScrollConsumed;
+ }
+ consumed[0] = 0;
+ consumed[1] = 0;
+ mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
+
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return consumed[0] != 0 || consumed[1] != 0;
+ } else if (offsetInWindow != null) {
+ offsetInWindow[0] = 0;
+ offsetInWindow[1] = 0;
}
- return consumed[0] != 0 || consumed[1] != 0;
}
return false;
}
@@ -18211,18 +18225,24 @@
/**
* Dispatch a fling to a nested scrolling parent.
*
- * <p>If a nested scrolling child view would normally fling but it is at the edge of its
- * own content it should use this method to delegate the fling to its nested scrolling parent.
- * The view implementation can use a {@link VelocityTracker} to obtain the velocity values
- * to pass.</p>
+ * <p>This method should be used to indicate that a nested scrolling child has detected
+ * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
*
* @param velocityX Horizontal fling velocity in pixels per second
* @param velocityY Vertical fling velocity in pixels per second
- * @return true if the nested scrolling parent consumed the fling
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
*/
- public boolean dispatchNestedFling(float velocityX, float velocityY) {
+ public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
- return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY);
+ return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY, consumed);
}
return false;
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 5112b9a..48a5bd5 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -287,8 +287,6 @@
mEdgeSlop = (int) (sizeAndDensity * EDGE_SLOP + 0.5f);
mFadingEdgeLength = (int) (sizeAndDensity * FADING_EDGE_LENGTH + 0.5f);
- mMinimumFlingVelocity = (int) (density * MINIMUM_FLING_VELOCITY + 0.5f);
- mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f);
mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
@@ -339,6 +337,11 @@
mPagingTouchSlop = mTouchSlop * 2;
mDoubleTapTouchSlop = mTouchSlop;
+
+ mMinimumFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMinFlingVelocity);
+ mMaximumFlingVelocity = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_viewMaxFlingVelocity);
}
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8865ab4..43bc0b6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2342,7 +2342,6 @@
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
- stopNestedScroll();
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
@@ -5914,7 +5913,7 @@
* @inheritDoc
*/
@Override
- public boolean onNestedFling(View target, float velocityX, float velocityY) {
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 3cd6449..588b9cd 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -512,14 +512,21 @@
/**
* Request a fling from a nested scroll.
*
+ * <p>This method signifies that a nested scrolling child has detected suitable conditions
+ * for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
* <p>If a nested scrolling child view would normally fling but it is at the edge of
- * its own content, it can delegate the fling to its nested scrolling parent instead.
- * This method allows the parent to optionally consume the fling.</p>
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
*
* @param target View that initiated the nested scroll
* @param velocityX Horizontal velocity in pixels per second.
* @param velocityY Vertical velocity in pixels per second
- * @return true if this parent consumed the fling
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if this parent consumed or otherwise reacted to the fling
*/
- public boolean onNestedFling(View target, float velocityX, float velocityY);
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 14e422c..beebeb1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -234,6 +234,7 @@
InputStage mFirstInputStage;
InputStage mFirstPostImeInputStage;
+ InputStage mSyntheticInputStage;
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
@@ -599,8 +600,8 @@
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
- InputStage syntheticInputStage = new SyntheticInputStage();
- InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticInputStage);
+ mSyntheticInputStage = new SyntheticInputStage();
+ InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
@@ -3007,6 +3008,7 @@
private final static int MSG_INVALIDATE_WORLD = 23;
private final static int MSG_WINDOW_MOVED = 24;
private final static int MSG_FLUSH_LAYER_UPDATES = 25;
+ private final static int MSG_SYNTHESIZE_INPUT_EVENT = 26;
final class ViewRootHandler extends Handler {
@Override
@@ -3056,6 +3058,8 @@
return "MSG_WINDOW_MOVED";
case MSG_FLUSH_LAYER_UPDATES:
return "MSG_FLUSH_LAYER_UPDATES";
+ case MSG_SYNTHESIZE_INPUT_EVENT:
+ return "MSG_SYNTHESIZE_INPUT_EVENT";
}
return super.getMessageName(message);
}
@@ -3218,6 +3222,10 @@
enqueueInputEvent(event, receiver, 0, true);
args.recycle();
} break;
+ case MSG_SYNTHESIZE_INPUT_EVENT: {
+ InputEvent event = (InputEvent)msg.obj;
+ enqueueInputEvent(event, null, QueuedInputEvent.FLAG_UNHANDLED, true);
+ } break;
case MSG_DISPATCH_KEY_FROM_IME: {
if (LOCAL_LOGV) Log.v(
TAG, "Dispatching key "
@@ -3227,7 +3235,8 @@
// The IME is trying to say this event is from the
// system! Bad bad bad!
//noinspection UnusedAssignment
- event = KeyEvent.changeFlags(event, event.getFlags() & ~KeyEvent.FLAG_FROM_SYSTEM);
+ event = KeyEvent.changeFlags(event, event.getFlags() &
+ ~KeyEvent.FLAG_FROM_SYSTEM);
}
enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
} break;
@@ -4023,6 +4032,7 @@
private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
private final SyntheticTouchNavigationHandler mTouchNavigation =
new SyntheticTouchNavigationHandler();
+ private final SyntheticKeyboardHandler mKeyboard = new SyntheticKeyboardHandler();
public SyntheticInputStage() {
super(null);
@@ -4045,7 +4055,11 @@
mTouchNavigation.process(event);
return FINISH_HANDLED;
}
+ } else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) {
+ mKeyboard.process((KeyEvent)q.mEvent);
+ return FINISH_HANDLED;
}
+
return FORWARD;
}
@@ -4882,6 +4896,33 @@
};
}
+ final class SyntheticKeyboardHandler {
+ public void process(KeyEvent event) {
+ if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) {
+ return;
+ }
+
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ final int keyCode = event.getKeyCode();
+ final int metaState = event.getMetaState();
+
+ // Check for fallback actions specified by the key character map.
+ KeyCharacterMap.FallbackAction fallbackAction =
+ kcm.getFallbackAction(keyCode, metaState);
+ if (fallbackAction != null) {
+ final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
+ KeyEvent fallbackEvent = KeyEvent.obtain(
+ event.getDownTime(), event.getEventTime(),
+ event.getAction(), fallbackAction.keyCode,
+ event.getRepeatCount(), fallbackAction.metaState,
+ event.getDeviceId(), event.getScanCode(),
+ flags, event.getSource(), null);
+ fallbackAction.recycle();
+ enqueueInputEvent(fallbackEvent);
+ }
+ }
+ }
+
/**
* Returns true if the key is used for keyboard navigation.
* @param keyEvent The key event.
@@ -5461,6 +5502,7 @@
public static final int FLAG_FINISHED = 1 << 2;
public static final int FLAG_FINISHED_HANDLED = 1 << 3;
public static final int FLAG_RESYNTHESIZED = 1 << 4;
+ public static final int FLAG_UNHANDLED = 1 << 5;
public QueuedInputEvent mNext;
@@ -5475,6 +5517,14 @@
return mEvent instanceof MotionEvent
&& mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
}
+
+ public boolean shouldSendToSynthesizer() {
+ if ((mFlags & FLAG_UNHANDLED) != 0) {
+ return true;
+ }
+
+ return false;
+ }
}
private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
@@ -5578,7 +5628,13 @@
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
- InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
+ InputStage stage;
+ if (q.shouldSendToSynthesizer()) {
+ stage = mSyntheticInputStage;
+ } else {
+ stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
+ }
+
if (stage != null) {
stage.deliver(q);
} else {
@@ -5810,43 +5866,29 @@
mHandler.sendMessage(msg);
}
+ public void synthesizeInputEvent(InputEvent event) {
+ Message msg = mHandler.obtainMessage(MSG_SYNTHESIZE_INPUT_EVENT, event);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+
public void dispatchKeyFromIme(KeyEvent event) {
Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_IME, event);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
- public void dispatchUnhandledKey(KeyEvent event) {
- if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
- // Some fallback keys are decided by the ViewRoot as they might have special
- // properties (e.g. are locale aware). These take precedence over fallbacks defined by
- // the kcm.
- final KeyCharacterMap kcm = event.getKeyCharacterMap();
- final int keyCode = event.getKeyCode();
- final int metaState = event.getMetaState();
-
- // Check for fallback actions specified by the key character map.
- KeyCharacterMap.FallbackAction fallbackAction =
- kcm.getFallbackAction(keyCode, metaState);
- if (fallbackAction != null) {
- final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
- KeyEvent fallbackEvent = KeyEvent.obtain(
- event.getDownTime(), event.getEventTime(),
- event.getAction(), fallbackAction.keyCode,
- event.getRepeatCount(), fallbackAction.metaState,
- event.getDeviceId(), event.getScanCode(),
- flags, event.getSource(), null);
- fallbackAction.recycle();
- dispatchInputEvent(fallbackEvent);
- }
- }
- }
-
+ /**
+ * Reinject unhandled {@link InputEvent}s in order to synthesize fallbacks events.
+ *
+ * Note that it is the responsibility of the caller of this API to recycle the InputEvent it
+ * passes in.
+ */
public void dispatchUnhandledInputEvent(InputEvent event) {
- if (event instanceof KeyEvent) {
- dispatchUnhandledKey((KeyEvent) event);
- return;
+ if (event instanceof MotionEvent) {
+ event = MotionEvent.obtain((MotionEvent) event);
}
+ synthesizeInputEvent(event);
}
public void dispatchAppVisibility(boolean visible) {
@@ -6131,7 +6173,7 @@
}
@Override
- public boolean onNestedFling(View target, float velocityX, float velocityY) {
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 688c251..33a6df6 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -19,6 +19,7 @@
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Message;
+import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.ViewRootImpl;
@@ -272,11 +273,42 @@
*
* @param view The WebView that is initiating the callback.
* @param event The key event.
+ * @deprecated This method is subsumed by the more generic onUnhandledInputEvent.
*/
+ @Deprecated
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
+ onUnhandledInputEventInternal(view, event);
+ }
+
+ /**
+ * Notify the host application that a input event was not handled by the WebView.
+ * Except system keys, WebView always consumes input events in the normal flow
+ * or if shouldOverrideKeyEvent returns true. This is called asynchronously
+ * from where the event is dispatched. It gives the host application a chance
+ * to handle the unhandled input events.
+ *
+ * Note that if the event is a {@link MotionEvent}, then it's lifetime is only that of the
+ * function call. If the WebViewClient wishes to use the event beyond that, then it <i>must</i>
+ * create a copy of the event.
+ *
+ * It is the responsibility of overriders of this method to call {@link onUnhandledKeyEvent}
+ * when appropriate if they wish to continue receiving events through it.
+ *
+ * @param view The WebView that is initiating the callback.
+ * @param event The input event.
+ */
+ public void onUnhandledInputEvent(WebView view, InputEvent event) {
+ if (event instanceof KeyEvent) {
+ onUnhandledKeyEvent(view, (KeyEvent) event);
+ return;
+ }
+ onUnhandledInputEventInternal(view, event);
+ }
+
+ private void onUnhandledInputEventInternal(WebView view, InputEvent event) {
ViewRootImpl root = view.getViewRootImpl();
if (root != null) {
- root.dispatchUnhandledKey(event);
+ root.dispatchUnhandledInputEvent(event);
}
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 7e8f6b4..3e46f68 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1565,10 +1565,10 @@
}
private void flingWithNestedDispatch(int velocityY) {
- if (mScrollY == 0 && velocityY < 0 ||
- mScrollY == getScrollRange() && velocityY > 0) {
- dispatchNestedFling(0, velocityY);
- } else {
+ final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
+ (mScrollY < getScrollRange() || velocityY < 0);
+ dispatchNestedFling(0, velocityY, canFling);
+ if (canFling) {
fling(velocityY);
}
}
@@ -1627,6 +1627,12 @@
return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
}
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ super.onNestedScrollAccepted(child, target, axes);
+ startNestedScroll(SCROLL_AXIS_VERTICAL);
+ }
+
/**
* @inheritDoc
*/
@@ -1638,16 +1644,23 @@
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
+ final int oldScrollY = mScrollY;
scrollBy(0, dyUnconsumed);
+ final int myConsumed = mScrollY - oldScrollY;
+ final int myUnconsumed = dyUnconsumed - myConsumed;
+ dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
}
/**
* @inheritDoc
*/
@Override
- public boolean onNestedFling(View target, float velocityX, float velocityY) {
- flingWithNestedDispatch((int) velocityY);
- return true;
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ if (!consumed) {
+ flingWithNestedDispatch((int) velocityY);
+ return true;
+ }
+ return false;
}
@Override
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 131f828..66548f0 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -17,7 +17,9 @@
package com.android.internal.app;
import android.animation.ValueAnimator;
+import android.content.res.TypedArray;
import android.view.ViewParent;
+import com.android.internal.R;
import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
@@ -41,7 +43,6 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ContextThemeWrapper;
@@ -57,7 +58,6 @@
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Map;
/**
* WindowDecorActionBar is the ActionBar implementation used
@@ -66,7 +66,8 @@
* across both the ActionBarView at the top of the screen and
* a horizontal LinearLayout at the bottom which is normally hidden.
*/
-public class WindowDecorActionBar extends ActionBar {
+public class WindowDecorActionBar extends ActionBar implements
+ ActionBarOverlayLayout.ActionBarVisibilityCallback {
private static final String TAG = "WindowDecorActionBar";
private Context mContext;
@@ -116,6 +117,7 @@
private Animator mCurrentShowAnim;
private boolean mShowHideAnimationEnabled;
+ boolean mHideOnContentScroll;
final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@Override
@@ -132,7 +134,7 @@
mCurrentShowAnim = null;
completeDeferredDestroyActionMode();
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
};
@@ -183,7 +185,7 @@
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
com.android.internal.R.id.action_bar_overlay_layout);
if (mOverlayLayout != null) {
- mOverlayLayout.setActionBar(this);
+ mOverlayLayout.setActionBarVisibilityCallback(this);
}
mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
mContextView = (ActionBarContextView) decor.findViewById(
@@ -213,6 +215,14 @@
ActionBarPolicy abp = ActionBarPolicy.get(mContext);
setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
setHasEmbeddedTabs(abp.hasEmbeddedTabs());
+
+ final TypedArray a = mContext.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.ActionBar,
+ com.android.internal.R.attr.actionBarStyle, 0);
+ if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
+ setHideOnContentScrollEnabled(true);
+ }
+ a.recycle();
}
public void onConfigurationChanged(Configuration newConfig) {
@@ -234,17 +244,14 @@
if (isInTabMode) {
mTabScrollView.setVisibility(View.VISIBLE);
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
} else {
mTabScrollView.setVisibility(View.GONE);
}
}
mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode);
- }
-
- public boolean hasNonEmbeddedTabs() {
- return !mHasEmbeddedTabs && getNavigationMode() == NAVIGATION_MODE_TABS;
+ mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode);
}
private void ensureTabsExist() {
@@ -279,7 +286,7 @@
}
}
- public void setWindowVisibility(int visibility) {
+ public void onWindowVisibilityChanged(int visibility) {
mCurWindowVisibility = visibility;
}
@@ -453,6 +460,7 @@
mActionMode.finish();
}
+ mOverlayLayout.setHideOnContentScrollEnabled(false);
mContextView.killMode();
ActionModeImpl mode = new ActionModeImpl(callback);
if (mode.dispatchOnCreate()) {
@@ -464,7 +472,7 @@
if (mSplitView.getVisibility() != View.VISIBLE) {
mSplitView.setVisibility(View.VISIBLE);
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
}
@@ -652,6 +660,35 @@
}
}
+ @Override
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) {
+ throw new IllegalStateException("Action bar must be in overlay mode " +
+ "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll");
+ }
+ mHideOnContentScroll = hideOnContentScroll;
+ mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll);
+ }
+
+ @Override
+ public boolean isHideOnContentScrollEnabled() {
+ return mOverlayLayout.isHideOnContentScrollEnabled();
+ }
+
+ @Override
+ public int getHideOffset() {
+ return mOverlayLayout.getActionBarHideOffset();
+ }
+
+ @Override
+ public void setHideOffset(int offset) {
+ if (offset != 0 && !mOverlayLayout.isInOverlayMode()) {
+ throw new IllegalStateException("Action bar must be in overlay mode " +
+ "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset");
+ }
+ mOverlayLayout.setActionBarHideOffset(offset);
+ }
+
private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem,
boolean showingForMode) {
if (showingForMode) {
@@ -737,7 +774,7 @@
mShowListener.onAnimationEnd(null);
}
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
@@ -781,11 +818,7 @@
}
public boolean isShowing() {
- return mNowShowing;
- }
-
- public boolean isSystemShowing() {
- return !mHiddenBySystem;
+ return mNowShowing && getHideOffset() < getHeight();
}
void animateToMode(boolean toActionMode) {
@@ -844,6 +877,18 @@
mActionView.setHomeActionContentDescription(resId);
}
+ @Override
+ public void onContentScrollStarted() {
+ if (mCurrentShowAnim != null) {
+ mCurrentShowAnim.cancel();
+ mCurrentShowAnim = null;
+ }
+ }
+
+ @Override
+ public void onContentScrollStopped() {
+ }
+
/**
* @hide
*/
@@ -894,6 +939,7 @@
// Clear out the context mode views after the animation finishes
mContextView.closeMode();
mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll);
mActionMode = null;
}
@@ -1204,6 +1250,7 @@
break;
}
mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
+ mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
}
@Override
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index c9dff1a..01bee0c 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -16,18 +16,22 @@
package com.android.internal.widget;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import com.android.internal.app.WindowDecorActionBar;
-
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.AttributeSet;
+import android.util.IntProperty;
+import android.util.Property;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
+import android.widget.OverScroller;
/**
* Special layout for the containing of an overlay action bar (and its
@@ -38,7 +42,7 @@
private static final String TAG = "ActionBarOverlayLayout";
private int mActionBarHeight;
- private WindowDecorActionBar mActionBar;
+ //private WindowDecorActionBar mActionBar;
private int mWindowVisibility = View.VISIBLE;
// The main UI elements that we handle the layout of.
@@ -54,6 +58,10 @@
private boolean mIgnoreWindowContentOverlay;
private boolean mOverlayMode;
+ private boolean mHasNonEmbeddedTabs;
+ private boolean mHideOnContentScroll;
+ private boolean mAnimatingForFling;
+ private int mHideOnContentScrollReference;
private int mLastSystemUiVisibility;
private final Rect mBaseContentInsets = new Rect();
private final Rect mLastBaseContentInsets = new Rect();
@@ -62,6 +70,84 @@
private final Rect mInnerInsets = new Rect();
private final Rect mLastInnerInsets = new Rect();
+ private ActionBarVisibilityCallback mActionBarVisibilityCallback;
+
+ private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms
+
+ private OverScroller mFlingEstimator;
+
+ private ViewPropertyAnimator mCurrentActionBarTopAnimator;
+ private ViewPropertyAnimator mCurrentActionBarBottomAnimator;
+
+ private final Animator.AnimatorListener mTopAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentActionBarTopAnimator = null;
+ mAnimatingForFling = false;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentActionBarTopAnimator = null;
+ mAnimatingForFling = false;
+ }
+ };
+
+ private final Animator.AnimatorListener mBottomAnimatorListener =
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentActionBarBottomAnimator = null;
+ mAnimatingForFling = false;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentActionBarBottomAnimator = null;
+ mAnimatingForFling = false;
+ }
+ };
+
+ private final Runnable mRemoveActionBarHideOffset = new Runnable() {
+ public void run() {
+ haltActionBarHideOffsetAnimations();
+ mCurrentActionBarTopAnimator = mActionBarTop.animate().translationY(0)
+ .setListener(mTopAnimatorListener);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ mCurrentActionBarBottomAnimator = mActionBarBottom.animate().translationY(0)
+ .setListener(mBottomAnimatorListener);
+ }
+ }
+ };
+
+ private final Runnable mAddActionBarHideOffset = new Runnable() {
+ public void run() {
+ haltActionBarHideOffsetAnimations();
+ mCurrentActionBarTopAnimator = mActionBarTop.animate()
+ .translationY(-mActionBarTop.getHeight())
+ .setListener(mTopAnimatorListener);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ mCurrentActionBarBottomAnimator = mActionBarBottom.animate()
+ .translationY(mActionBarBottom.getHeight())
+ .setListener(mBottomAnimatorListener);
+ }
+ }
+ };
+
+ public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET =
+ new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") {
+
+ @Override
+ public void setValue(ActionBarOverlayLayout object, int value) {
+ object.setActionBarHideOffset(value);
+ }
+
+ @Override
+ public Integer get(ActionBarOverlayLayout object) {
+ return object.getActionBarHideOffset();
+ }
+ };
+
static final int[] ATTRS = new int [] {
com.android.internal.R.attr.actionBarSize,
com.android.internal.R.attr.windowContentOverlay
@@ -86,14 +172,22 @@
mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT;
+
+ mFlingEstimator = new OverScroller(context);
}
- public void setActionBar(WindowDecorActionBar impl) {
- mActionBar = impl;
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ haltActionBarHideOffsetAnimations();
+ }
+
+ public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) {
+ mActionBarVisibilityCallback = cb;
if (getWindowToken() != null) {
// This is being initialized after being added to a window;
// make sure to update all state now.
- mActionBar.setWindowVisibility(mWindowVisibility);
+ mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility);
if (mLastSystemUiVisibility != 0) {
int newVis = mLastSystemUiVisibility;
onWindowSystemUiVisibilityChanged(newVis);
@@ -114,6 +208,14 @@
Build.VERSION_CODES.KITKAT;
}
+ public boolean isInOverlayMode() {
+ return mOverlayMode;
+ }
+
+ public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) {
+ mHasNonEmbeddedTabs = hasNonEmbeddedTabs;
+ }
+
public void setShowingForActionMode(boolean showing) {
if (showing) {
// Here's a fun hack: if the status bar is currently being hidden,
@@ -140,19 +242,18 @@
pullChildren();
final int diff = mLastSystemUiVisibility ^ visible;
mLastSystemUiVisibility = visible;
- final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
- final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
- final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
- if (mActionBar != null) {
+ final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0;
+ final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+ if (mActionBarVisibilityCallback != null) {
// We want the bar to be visible if it is not being hidden,
// or the app has not turned on a stable UI mode (meaning they
// are performing explicit layout around the action bar).
- mActionBar.enableContentAnimations(!stable);
- if (barVisible || !stable) mActionBar.showForSystem();
- else mActionBar.hideForSystem();
+ mActionBarVisibilityCallback.enableContentAnimations(!stable);
+ if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
+ else mActionBarVisibilityCallback.hideForSystem();
}
- if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
- if (mActionBar != null) {
+ if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ if (mActionBarVisibilityCallback != null) {
requestApplyInsets();
}
}
@@ -162,8 +263,8 @@
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility;
- if (mActionBar != null) {
- mActionBar.setWindowVisibility(visibility);
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility);
}
}
@@ -279,8 +380,8 @@
// This is the standard space needed for the action bar. For stable measurement,
// we can't depend on the size currently reported by it -- this must remain constant.
topInset = mActionBarHeight;
- if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
- View tabs = mActionBarTop.getTabContainer();
+ if (mHasNonEmbeddedTabs) {
+ final View tabs = mActionBarTop.getTabContainer();
if (tabs != null) {
// If tabs are not embedded, increase space on top to account for them.
topInset += mActionBarHeight;
@@ -395,16 +496,138 @@
return false;
}
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int axes) {
+ if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) {
+ return false;
+ }
+ return mHideOnContentScroll;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ super.onNestedScrollAccepted(child, target, axes);
+ mHideOnContentScrollReference = getActionBarHideOffset();
+ haltActionBarHideOffsetAnimations();
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onContentScrollStarted();
+ }
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ mHideOnContentScrollReference += dyConsumed;
+ setActionBarHideOffset(mHideOnContentScrollReference);
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ super.onStopNestedScroll(target);
+ if (mHideOnContentScroll && !mAnimatingForFling) {
+ if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) {
+ postRemoveActionBarHideOffset();
+ } else {
+ postAddActionBarHideOffset();
+ }
+ }
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onContentScrollStopped();
+ }
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ if (!mHideOnContentScroll || !consumed) {
+ return false;
+ }
+ if (shouldHideActionBarOnFling(velocityX, velocityY)) {
+ addActionBarHideOffset();
+ } else {
+ removeActionBarHideOffset();
+ }
+ mAnimatingForFling = true;
+ return true;
+ }
+
void pullChildren() {
if (mContent == null) {
mContent = findViewById(com.android.internal.R.id.content);
- mActionBarTop = (ActionBarContainer)findViewById(
+ mActionBarTop = (ActionBarContainer) findViewById(
com.android.internal.R.id.action_bar_container);
mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
}
}
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll != mHideOnContentScroll) {
+ mHideOnContentScroll = hideOnContentScroll;
+ if (!hideOnContentScroll) {
+ stopNestedScroll();
+ haltActionBarHideOffsetAnimations();
+ setActionBarHideOffset(0);
+ }
+ }
+ }
+
+ public boolean isHideOnContentScrollEnabled() {
+ return mHideOnContentScroll;
+ }
+
+ public int getActionBarHideOffset() {
+ return -((int) mActionBarTop.getTranslationY());
+ }
+
+ public void setActionBarHideOffset(int offset) {
+ haltActionBarHideOffsetAnimations();
+ final int topHeight = mActionBarTop.getHeight();
+ offset = Math.max(0, Math.min(offset, topHeight));
+ mActionBarTop.setTranslationY(-offset);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ // Match the hide offset proportionally for a split bar
+ final float fOffset = (float) offset / topHeight;
+ final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset);
+ mActionBarBottom.setTranslationY(bOffset);
+ }
+ }
+
+ private void haltActionBarHideOffsetAnimations() {
+ removeCallbacks(mRemoveActionBarHideOffset);
+ removeCallbacks(mAddActionBarHideOffset);
+ if (mCurrentActionBarTopAnimator != null) {
+ mCurrentActionBarTopAnimator.cancel();
+ }
+ if (mCurrentActionBarBottomAnimator != null) {
+ mCurrentActionBarBottomAnimator.cancel();
+ }
+ }
+
+ private void postRemoveActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
+ }
+
+ private void postAddActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
+ }
+
+ private void removeActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ mRemoveActionBarHideOffset.run();
+ }
+
+ private void addActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ mAddActionBarHideOffset.run();
+ }
+
+ private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) {
+ mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ final int finalY = mFlingEstimator.getFinalY();
+ return finalY > mActionBarTop.getHeight();
+ }
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
@@ -423,4 +646,13 @@
super(source);
}
}
+
+ public interface ActionBarVisibilityCallback {
+ void onWindowVisibilityChanged(int visibility);
+ void showForSystem();
+ void hideForSystem();
+ void enableContentAnimations(boolean enable);
+ void onContentScrollStarted();
+ void onContentScrollStopped();
+ }
}
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index 1dd4608..e71fa4a 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -30,4 +30,11 @@
<!-- Base "touch slop" value used by ViewConfiguration as a
movement threshold where scrolling should begin. -->
<dimen name="config_viewConfigurationTouchSlop">4dp</dimen>
+
+ <!-- Minimum velocity to initiate a fling, as measured in dips per second. -->
+ <dimen name="config_viewMinFlingVelocity">50dp</dimen>
+
+ <!-- Maximum velocity to initiate a fling, as measured in dips per second. -->
+ <dimen name="config_viewMaxFlingVelocity">8000dp</dimen>
+
</resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index efc1b55..e07ebd4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -966,7 +966,6 @@
<attr name="colorButtonPressed" format="color" />
<attr name="colorButtonNormalColored" format="color" />
<attr name="colorButtonPressedColored" format="color" />
-
</declare-styleable>
<!-- **************************************************************** -->
@@ -6348,6 +6347,8 @@
<!-- Specifies padding that should be applied to the left and right sides of
system-provided items in the bar. -->
<attr name="itemPadding" format="dimension" />
+ <!-- Set true to hide the action bar on a vertical nested scroll of content. -->
+ <attr name="hideOnContentScroll" format="boolean" />
</declare-styleable>
<declare-styleable name="ActionMode">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f919c9f..624ed73 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1206,6 +1206,12 @@
movement threshold where scrolling should begin. -->
<dimen name="config_viewConfigurationTouchSlop">8dp</dimen>
+ <!-- Minimum velocity to initiate a fling, as measured in dips per second. -->
+ <dimen name="config_viewMinFlingVelocity">50dp</dimen>
+
+ <!-- Maximum velocity to initiate a fling, as measured in dips per second. -->
+ <dimen name="config_viewMaxFlingVelocity">8000dp</dimen>
+
<!-- Maximum number of grid columns permitted in the ResolverActivity
used for picking activities to handle an intent. -->
<integer name="config_maxResolverActivityColumns">2</integer>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5ccb05b..d4b3f0d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2166,6 +2166,7 @@
<public type="attr" name="elevation" />
<public type="attr" name="excludeId" />
<public type="attr" name="excludeClass" />
+ <public type="attr" name="hideOnContentScroll" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d4ac74a..e92d8c6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -328,6 +328,8 @@
<java-symbol type="dimen" name="accessibility_touch_slop" />
<java-symbol type="dimen" name="config_prefDialogWidth" />
<java-symbol type="dimen" name="config_viewConfigurationTouchSlop" />
+ <java-symbol type="dimen" name="config_viewMinFlingVelocity" />
+ <java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
<java-symbol type="dimen" name="default_app_widget_padding_bottom" />
<java-symbol type="dimen" name="default_app_widget_padding_left" />
<java-symbol type="dimen" name="default_app_widget_padding_right" />
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 01d22ee..1ef74ba 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -492,11 +492,11 @@
}
key BUTTON_X {
- base: fallback DPAD_CENTER
+ base: fallback DEL
}
key BUTTON_Y {
- base: fallback BACK
+ base: fallback SPACE
}
key BUTTON_Z {
diff --git a/docs/html/wear/images/11_bundles_A.png b/docs/html/wear/images/11_bundles_A.png
index 0ffc6ec..7199a1f 100644
--- a/docs/html/wear/images/11_bundles_A.png
+++ b/docs/html/wear/images/11_bundles_A.png
Binary files differ
diff --git a/docs/html/wear/images/11_bundles_B.png b/docs/html/wear/images/11_bundles_B.png
index c188d3d..bb751a2 100644
--- a/docs/html/wear/images/11_bundles_B.png
+++ b/docs/html/wear/images/11_bundles_B.png
Binary files differ
diff --git a/docs/html/wear/images/11_bundles_C.png b/docs/html/wear/images/11_bundles_C.png
deleted file mode 100644
index de71c59..0000000
--- a/docs/html/wear/images/11_bundles_C.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/wear/notifications/stacks.jd b/docs/html/wear/notifications/stacks.jd
index 83e11c1..7f955f6 100644
--- a/docs/html/wear/notifications/stacks.jd
+++ b/docs/html/wear/notifications/stacks.jd
@@ -2,8 +2,7 @@
@jd:body
-<img src="{@docRoot}wear/images/11_bundles_C.png" height="154" width="273" style="float:right;margin:0 0 20px 40px"/>
-<img src="{@docRoot}wear/images/11_bundles_B.png" height="200" width="169" style="clear:both;float:right;margin:0 0 20px 40px" />
+<img src="{@docRoot}wear/images/11_bundles_B.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" />
<img src="{@docRoot}wear/images/11_bundles_A.png" height="200" width="169" style="float:right;margin:0 0 20px 40px" />
<p>When creating notifications for a handheld device, you should always aggregate similar
@@ -11,7 +10,7 @@
for received messages, you should not show more than one notification
on a handheld device—when more than one is message is received, use a single notification
to provide a summary such as "2 new messages."</p>
-<br />
+
<p>However, a summary notification is less useful on an Android wearable because users
are not able to read details from each message on the wearable (they must open your app on the
handheld to view more information). So for the wearable device, you should
@@ -26,50 +25,25 @@
Wear</a>.</p>
-<h2 id="AddGroup">Create notifications in stacks</h2>
-
-<div style="float:right">
- <script src="//ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
- <div style="width:440;height:246; padding-left:20px">
- <object type="application/x-shockwave-flash" id="ytapiplayer" data="//www.youtube.com/v/L4LvKOTkZ7Q?enablejsapi=1&playerapiid=ytplayer&version=3&HD=1;rel=0;showinfo=0;modestbranding;origin=developer.android.com;autohide=1" width="440" height="246" style="visibility: visible;"><param name="allowScriptAccess" value="always"></object>
- <script type="text/javascript">
- var params = { allowScriptAccess: "always" };
- var atts = { id: "ytapiplayer" };
- swfobject.embedSWF("//www.youtube.com/v/L4LvKOTkZ7Q?enablejsapi=1&playerapiid=ytplayer&version=3&HD=1;rel=0;showinfo=0;modestbranding;origin=developer.android.com;autohide=1",
- "ytapiplayer", "440", "246", "8", null, null, params, atts);
-
- // Callback used to pause/resume carousel based on video state
- function onytplayerStateChange(newState) {
- var isPaused = $("#pauseButton").hasClass("paused");
- if ((newState == 1) || (newState == 3)) {
- // if playing or buffering, pause the carousel
- if (!isPaused) {
- $("#pauseButton").click();
- }
- } else {
- // otherwise, make sure carousel is running
- if (isPaused) {
- $("#pauseButton").click();
- }
- }
- }
-
- // Callback received when YouTube player loads to setup callback (above)
- function onYouTubePlayerReady(playerId) {
- var ytplayer = document.getElementById("ytapiplayer");
- ytplayer.addEventListener("onStateChange", "onytplayerStateChange");
- }
-
- </script>
- </div>
-</div>
-
+<h2 id="AddGroup">Add Each Notification to a Group</h2>
<p>To create a stack, call <a
href="{@docRoot}reference/android/preview/support/wearable/notifications/WearableNotifications.Builder.html#setGroup(java.lang.String, int)">
<code>setGroup()</code></a> for each notification you want in the stack, passing the same
-group key.</p>
+group key. For example:</p>
+<pre style="clear:right">
+final static String GROUP_KEY_EMAILS = "group_key_emails";
+
+NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
+ .setContentTitle("New mail from " + sender)
+ .setContentText(subject)
+ .setSmallIcon(R.drawable.new_mail);
+
+Notification notif = new WearableNotifications.Builder(builder)
+ .setGroup(GROUP_KEY_EMAILS)
+ .build();
+</pre>
<p>By default, notifications appear in the order in which you added them, with the most recent
notification visible at the top. You can define a specific position in the group
@@ -78,82 +52,21 @@
<code>setGroup()</code></a>.</p>
-<p>It's also important that you still provide a summary notification that appears on handheld devices.
+<h2 id="AddSummary">Add a Summary Notification</h2>
+
+<p>It's important that you still provide a summary notification that appears on handheld devices.
So in addition to adding each unique notification to the same stack group, also add a summary
notification, but set its order position to be <a
href="{@docRoot}reference/android/preview/support/wearable/notifications/WearableNotifications.html#GROUP_ORDER_SUMMARY"><code>GROUP_ORDER_SUMMARY</code></a>.</p>
-<p>This notification will not appear in your stack of notifications on the wearable, but
-appears as the only notification on the handheld device.
-</p>
-
-
-<p>Here's an example that creates a stack notification for a wearable and
-a summary notification for a handset device:</p>
-
-<pre style="clear:right">
-public void sendNotifications() {
- Bitmap bitmapMila = BitmapFactory.decodeResource(getResources(),
- R.drawable.mila128);
-
- // Nuke all previous notifications and generate unique ids
- NotificationManagerCompat.from(this).cancelAll();
- int notificationId = 0;
-
- // String to represent the group all the notifications will be a part of
- final String GROUP_KEY_EMAILS = "group_key_messages";
-
- // Group notification that will be visible on the phone
- NotificationCompat.Builder builderG = new NotificationCompat.Builder(this)
- .setContentTitle("2 Pet Notifications")
- .setContentText("Mila and Dylan both sent messages")
- .setSmallIcon(R.drawable.ic_launcher)
- .setLargeIcon(bitmapMila);
- Notification summaryNotification = new WearableNotifications
- .Builder(builderG)
- .setGroup(GROUP_KEY_EMAILS,
- WearableNotifications.GROUP_ORDER_SUMMARY)
- .build();
-
- // Separate notifications that will be visible on the watch
- Intent viewIntent1 = new Intent(this, MainActivity.class);
- PendingIntent viewPendingIntent1 =
- PendingIntent.getActivity(this, notificationId+1, viewIntent1, 0);
- NotificationCompat.Builder builder1 = new NotificationCompat.Builder(this)
- .addAction(R.drawable.ic_action_done, "Treat Fed",
- viewPendingIntent1)
- .setContentTitle("Message from Mila")
- .setContentText("What's for dinner? "
- + "Can we have steak?")
- .setSmallIcon(R.drawable.ic_launcher);
- Notification notification1 = new WearableNotifications.Builder(builder1)
- .setGroup(GROUP_KEY_EMAILS)
- .build();
-
- Intent viewIntent2 = new Intent(this, MainActivity.class);
- PendingIntent viewPendingIntent2 =
- PendingIntent.getActivity(this, notificationId+2, viewIntent2, 0);
- NotificationCompat.Builder builder2 = new NotificationCompat.Builder(this)
- .addAction(R.drawable.ic_action_done, "Water Filled",
- viewPendingIntent2)
- .setContentTitle("Message from Dylan")
- .setContentText("Can you refill our water bowl?")
- .setSmallIcon(R.drawable.ic_launcher);
- Notification notification2 = new WearableNotifications.Builder(builder2)
- .setGroup(GROUP_KEY_EMAILS)
- .build();
-
- // Issue the group notification
- NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(this);
- notificationManager.notify(notificationId+0, summaryNotification);
-
- // Issue the separate wear notifications
- notificationManager.notify(notificationId+2, notification2);
- notificationManager.notify(notificationId+1, notification1);
-}
+<pre>
+Notification summaryNotification = new WearableNotifications.Builder(builder)
+ .setGroup(GROUP_KEY_EMAILS, WearableNotifications.GROUP_ORDER_SUMMARY)
+ .build();
</pre>
+<p>This notification will not appear in your stack of notifications on the wearable, but
+appears as the only notification on the handheld device.
</body>
</html>
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index e3f57e9..207834a 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -16,20 +16,21 @@
package android.graphics.drawable;
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
-import android.util.MathUtils;
-import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
/**
* Draws a Quantum Paper ripple.
*/
class Ripple {
- private static final TimeInterpolator INTERPOLATOR = new DecelerateInterpolator(2.0f);
+ private static final TimeInterpolator INTERPOLATOR = new DecelerateInterpolator();
/** Starting radius for a ripple. */
private static final int STARTING_RADIUS_DP = 16;
@@ -37,6 +38,9 @@
/** Radius when finger is outside view bounds. */
private static final int OUTSIDE_RADIUS_DP = 16;
+ /** Radius when finger is inside view bounds. */
+ private static final int INSIDE_RADIUS_DP = 96;
+
/** Margin when constraining outside touches (fraction of outer radius). */
private static final float OUTSIDE_MARGIN = 0.8f;
@@ -44,15 +48,52 @@
private static final float OUTSIDE_RESISTANCE = 0.7f;
/** Minimum alpha value during a pulse animation. */
- private static final int PULSE_MIN_ALPHA = 128;
+ private static final float PULSE_MIN_ALPHA = 0.5f;
+ /** Duration for animating the trailing edge of the ripple. */
+ private static final int EXIT_DURATION = 600;
+
+ /** Duration for animating the leading edge of the ripple. */
+ private static final int ENTER_DURATION = 400;
+
+ /** Duration for animating the ripple alpha in and out. */
+ private static final int FADE_DURATION = 50;
+
+ /** Minimum elapsed time between start of enter and exit animations. */
+ private static final int EXIT_MIN_DELAY = 200;
+
+ /** Duration for animating between inside and outside touch. */
+ private static final int OUTSIDE_DURATION = 300;
+
+ /** Duration for animating pulses. */
+ private static final int PULSE_DURATION = 400;
+
+ /** Interval between pulses while inside and fully entered. */
+ private static final int PULSE_INTERVAL = 400;
+
+ /** Delay before pulses start. */
+ private static final int PULSE_DELAY = 500;
+
+ private final Drawable mOwner;
+
+ /** Bounds used for computing max radius and containment. */
private final Rect mBounds;
- private final Rect mPadding;
- private RippleAnimator mAnimator;
+ /** Configured maximum ripple radius when the center is outside the bounds. */
+ private final int mMaxOutsideRadius;
- private int mMinRadius;
- private int mOutsideRadius;
+ /** Configured maximum ripple radius. */
+ private final int mMaxInsideRadius;
+
+ private ObjectAnimator mEnter;
+ private ObjectAnimator mExit;
+
+ /** Maximum ripple radius. */
+ private int mMaxRadius;
+
+ private float mOuterRadius;
+ private float mInnerRadius;
+ private float mAlphaMultiplier;
/** Center x-coordinate. */
private float mX;
@@ -61,46 +102,151 @@
private float mY;
/** Whether the center is within the parent bounds. */
- private boolean mInside;
+ private boolean mInsideBounds;
/** Whether to pulse this ripple. */
- boolean mPulse;
-
- /** Enter state. A value in [0...1] or -1 if not set. */
- float mEnterState = -1;
+ private boolean mPulseEnabled;
- /** Exit state. A value in [0...1] or -1 if not set. */
- float mExitState = -1;
+ /** Temporary hack since we can't check finished state of animator. */
+ private boolean mExitFinished;
- /** Outside state. A value in [0...1] or -1 if not set. */
- float mOutsideState = -1;
-
- /** Pulse state. A value in [0...1] or -1 if not set. */
- float mPulseState = -1;
+ /** Whether this ripple has ever moved. */
+ private boolean mHasMoved;
/**
- * Creates a new ripple with the specified parent bounds, padding, initial
- * position, and screen density.
+ * Creates a new ripple.
*/
- public Ripple(Rect bounds, Rect padding, float x, float y, float density, boolean pulse) {
+ public Ripple(Drawable owner, Rect bounds, float density, boolean pulseEnabled) {
+ mOwner = owner;
mBounds = bounds;
- mPadding = padding;
- mInside = mBounds.contains((int) x, (int) y);
- mPulse = pulse;
+ mPulseEnabled = pulseEnabled;
- mX = x;
- mY = y;
+ mOuterRadius = (int) (density * STARTING_RADIUS_DP + 0.5f);
+ mMaxOutsideRadius = (int) (density * OUTSIDE_RADIUS_DP + 0.5f);
+ mMaxInsideRadius = (int) (density * INSIDE_RADIUS_DP + 0.5f);
+ mMaxRadius = Math.min(mMaxInsideRadius, Math.max(bounds.width(), bounds.height()));
+ }
- mMinRadius = (int) (density * STARTING_RADIUS_DP + 0.5f);
- mOutsideRadius = (int) (density * OUTSIDE_RADIUS_DP + 0.5f);
+ public void setOuterRadius(float r) {
+ mOuterRadius = r;
+ invalidateSelf();
}
-
- public void setMinRadius(int minRadius) {
- mMinRadius = minRadius;
+
+ public float getOuterRadius() {
+ return mOuterRadius;
}
-
- public void setOutsideRadius(int outsideRadius) {
- mOutsideRadius = outsideRadius;
+
+ public void setInnerRadius(float r) {
+ mInnerRadius = r;
+ invalidateSelf();
+ }
+
+ public float getInnerRadius() {
+ return mInnerRadius;
+ }
+
+ public void setAlphaMultiplier(float a) {
+ mAlphaMultiplier = a;
+ invalidateSelf();
+ }
+
+ public float getAlphaMultiplier() {
+ return mAlphaMultiplier;
+ }
+
+ /**
+ * Returns whether this ripple has finished exiting.
+ */
+ public boolean isFinished() {
+ return mExitFinished;
+ }
+
+ /**
+ * Called when the bounds change.
+ */
+ public void onBoundsChanged() {
+ mMaxRadius = Math.min(mMaxInsideRadius, Math.max(mBounds.width(), mBounds.height()));
+
+ updateInsideBounds();
+ }
+
+ private void updateInsideBounds() {
+ final boolean insideBounds = mBounds.contains((int) (mX + 0.5f), (int) (mY + 0.5f));
+ if (mInsideBounds != insideBounds || !mHasMoved) {
+ mInsideBounds = insideBounds;
+ mHasMoved = true;
+
+ if (insideBounds) {
+ enter();
+ } else {
+ outside();
+ }
+ }
+ }
+
+ /**
+ * Draws the ripple using the specified paint.
+ */
+ public boolean draw(Canvas c, Paint p) {
+ final Rect bounds = mBounds;
+ final float outerRadius = mOuterRadius;
+ final float innerRadius = mInnerRadius;
+ final float alphaMultiplier = mAlphaMultiplier;
+
+ // Cache the paint alpha so we can restore it later.
+ final int paintAlpha = p.getAlpha();
+ final int alpha = (int) (paintAlpha * alphaMultiplier + 0.5f);
+
+ // Apply resistance effect when outside bounds.
+ final float x;
+ final float y;
+ if (mInsideBounds) {
+ x = mX;
+ y = mY;
+ } else {
+ // TODO: We need to do this outside of draw() so that our dirty
+ // bounds accurately reflect resistance.
+ x = looseConstrain(mX, bounds.left, bounds.right,
+ mOuterRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE);
+ y = looseConstrain(mY, bounds.top, bounds.bottom,
+ mOuterRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE);
+ }
+
+ final boolean hasContent;
+ if (alphaMultiplier <= 0 || innerRadius >= outerRadius) {
+ // Nothing to draw.
+ hasContent = false;
+ } else if (innerRadius > 0) {
+ // Draw a ring.
+ final float strokeWidth = outerRadius - innerRadius;
+ final float strokeRadius = innerRadius + strokeWidth / 2.0f;
+ p.setAlpha(alpha);
+ p.setStyle(Style.STROKE);
+ p.setStrokeWidth(strokeWidth);
+ c.drawCircle(x, y, strokeRadius, p);
+ hasContent = true;
+ } else if (outerRadius > 0) {
+ // Draw a circle.
+ p.setAlpha(alpha);
+ p.setStyle(Style.FILL);
+ c.drawCircle(x, y, outerRadius, p);
+ hasContent = true;
+ } else {
+ hasContent = false;
+ }
+
+ p.setAlpha(paintAlpha);
+ return hasContent;
+ }
+
+ /**
+ * Returns the maximum bounds for this ripple.
+ */
+ public void getBounds(Rect bounds) {
+ final int x = (int) mX;
+ final int y = (int) mY;
+ final int maxRadius = mMaxRadius;
+ bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius);
}
/**
@@ -110,117 +256,90 @@
mX = x;
mY = y;
- final boolean inside = mBounds.contains((int) x, (int) y);
- if (mInside != inside) {
- if (mAnimator != null) {
- mAnimator.outside();
- }
- mInside = inside;
- }
+ updateInsideBounds();
+ invalidateSelf();
}
- public void onBoundsChanged() {
- final boolean inside = mBounds.contains((int) mX, (int) mY);
- if (mInside != inside) {
- if (mAnimator != null) {
- mAnimator.outside();
- }
- mInside = inside;
+ /**
+ * Starts the exit animation. If {@link #enter()} was called recently, the
+ * animation may be postponed.
+ */
+ public void exit() {
+ mExitFinished = false;
+
+ final ObjectAnimator exit = ObjectAnimator.ofFloat(this, "innerRadius", 0, mMaxRadius);
+ exit.setAutoCancel(true);
+ exit.setDuration(EXIT_DURATION);
+ exit.setInterpolator(INTERPOLATOR);
+ exit.addListener(mAnimationListener);
+
+ if (mEnter != null && mEnter.isStarted()) {
+ // If we haven't been running the enter animation for long enough,
+ // delay the exit animator.
+ final int elapsed = (int) (mEnter.getAnimatedFraction() * mEnter.getDuration());
+ final int delay = Math.max(0, EXIT_MIN_DELAY - elapsed);
+ exit.setStartDelay(delay);
}
+
+ exit.start();
+
+ final ObjectAnimator fade = ObjectAnimator.ofFloat(this, "alphaMultiplier", 0);
+ fade.setAutoCancel(true);
+ fade.setDuration(EXIT_DURATION);
+ fade.start();
+
+ mExit = exit;
}
- public RippleAnimator animate() {
- if (mAnimator == null) {
- mAnimator = new RippleAnimator(this);
- }
- return mAnimator;
+ private void invalidateSelf() {
+ mOwner.invalidateSelf();
}
- public boolean draw(Canvas c, Paint p) {
- final Rect bounds = mBounds;
- final Rect padding = mPadding;
- final float dX = Math.max(mX - bounds.left, bounds.right - mX);
- final float dY = Math.max(mY - bounds.top, bounds.bottom - mY);
- final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY));
+ /**
+ * Starts the enter animation.
+ */
+ private void enter() {
+ final ObjectAnimator enter = ObjectAnimator.ofFloat(this, "outerRadius", mMaxRadius);
+ enter.setAutoCancel(true);
+ enter.setDuration(ENTER_DURATION);
+ enter.setInterpolator(INTERPOLATOR);
+ enter.start();
- final float enterState = mEnterState;
- final float exitState = mExitState;
- final float outsideState = mOutsideState;
- final float pulseState = mPulseState;
- final float insideRadius = MathUtils.lerp(mMinRadius, maxRadius, enterState);
- final float outerRadius = MathUtils.lerp(mOutsideRadius, insideRadius,
- mInside ? outsideState : 1 - outsideState);
+ final ObjectAnimator fade = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1);
+ fade.setAutoCancel(true);
+ fade.setDuration(FADE_DURATION);
+ fade.start();
- // Apply resistance effect when outside bounds.
- final float x = looseConstrain(mX, bounds.left + padding.left, bounds.right - padding.right,
- outerRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE);
- final float y = looseConstrain(mY, bounds.top + padding.top, bounds.bottom - padding.bottom,
- outerRadius * OUTSIDE_MARGIN, OUTSIDE_RESISTANCE);
-
- // Compute maximum alpha, taking pulse into account when active.
- final int maxAlpha;
- if (pulseState < 0 || pulseState >= 1) {
- maxAlpha = 255;
- } else {
- final float pulseAlpha;
- if (pulseState > 0.5) {
- // Pulsing in to max alpha.
- pulseAlpha = MathUtils.lerp(PULSE_MIN_ALPHA, 255, (pulseState - .5f) * 2);
- } else {
- // Pulsing out to min alpha.
- pulseAlpha = MathUtils.lerp(255, PULSE_MIN_ALPHA, pulseState * 2f);
- }
-
- if (exitState > 0) {
- // Animating exit, interpolate pulse with exit state.
- maxAlpha = (int) (MathUtils.lerp(255, pulseAlpha, exitState) + 0.5f);
- } else if (mInside) {
- // No animation, no need to interpolate.
- maxAlpha = (int) (pulseAlpha + 0.5f);
- } else {
- // Animating inside, interpolate pulse with inside state.
- maxAlpha = (int) (MathUtils.lerp(pulseAlpha, 255, outsideState) + 0.5f);
- }
+ // TODO: Starting with a delay will still cancel the fade in.
+ if (false && mPulseEnabled) {
+ final ObjectAnimator pulse = ObjectAnimator.ofFloat(
+ this, "alphaMultiplier", 1, PULSE_MIN_ALPHA);
+ pulse.setAutoCancel(true);
+ pulse.setDuration(PULSE_DURATION + PULSE_INTERVAL);
+ pulse.setRepeatCount(ObjectAnimator.INFINITE);
+ pulse.setRepeatMode(ObjectAnimator.REVERSE);
+ pulse.setStartDelay(PULSE_DELAY);
+ pulse.start();
}
- if (maxAlpha > 0) {
- if (exitState <= 0) {
- // Exit state isn't showing, so we can simplify to a solid
- // circle.
- if (outerRadius > 0) {
- p.setAlpha(maxAlpha);
- p.setStyle(Style.FILL);
- c.drawCircle(x, y, outerRadius, p);
- return true;
- }
- } else {
- // Both states are showing, so we need a circular stroke.
- final float innerRadius = MathUtils.lerp(0, outerRadius, exitState);
- final float strokeWidth = outerRadius - innerRadius;
- if (strokeWidth > 0) {
- final float strokeRadius = innerRadius + strokeWidth / 2f;
- final int alpha = (int) (MathUtils.lerp(maxAlpha, 0, exitState) + 0.5f);
- if (alpha > 0) {
- p.setAlpha(alpha);
- p.setStyle(Style.STROKE);
- p.setStrokeWidth(strokeWidth);
- c.drawCircle(x, y, strokeRadius, p);
- return true;
- }
- }
- }
- }
-
- return false;
+ mEnter = enter;
}
- public void getBounds(Rect bounds) {
- final int x = (int) mX;
- final int y = (int) mY;
- final int dX = Math.max(x, mBounds.right - x);
- final int dY = Math.max(x, mBounds.bottom - y);
- final int maxRadius = (int) Math.ceil(Math.sqrt(dX * dX + dY * dY));
- bounds.set(x - maxRadius, y - maxRadius, x + maxRadius, y + maxRadius);
+ /**
+ * Starts the outside transition animation.
+ */
+ private void outside() {
+ final float targetRadius = mMaxOutsideRadius;
+ final ObjectAnimator outside = ObjectAnimator.ofFloat(this, "outerRadius", targetRadius);
+ outside.setAutoCancel(true);
+ outside.setDuration(OUTSIDE_DURATION);
+ outside.setInterpolator(INTERPOLATOR);
+ outside.start();
+
+ final ObjectAnimator fade = ObjectAnimator.ofFloat(this, "alphaMultiplier", 1);
+ fade.setAutoCancel(true);
+ fade.setDuration(FADE_DURATION);
+ fade.start();
}
/**
@@ -229,6 +348,7 @@
*/
private static float looseConstrain(float value, float min, float max, float margin,
float factor) {
+ // TODO: Can we use actual spring physics here?
if (value < min) {
return min - Math.min(margin, (float) Math.pow(min - value, factor));
} else if (value > max) {
@@ -237,96 +357,28 @@
return value;
}
}
-
- public static class RippleAnimator {
- /** Duration for animating the trailing edge of the ripple. */
- private static final int EXIT_DURATION = 600;
- /** Duration for animating the leading edge of the ripple. */
- private static final int ENTER_DURATION = 400;
-
- /** Minimum elapsed time between start of enter and exit animations. */
- private static final int EXIT_MIN_DELAY = 200;
-
- /** Duration for animating between inside and outside touch. */
- private static final int OUTSIDE_DURATION = 300;
-
- /** Duration for animating pulses. */
- private static final int PULSE_DURATION = 400;
-
- /** Interval between pulses while inside and fully entered. */
- private static final int PULSE_INTERVAL = 400;
-
- /** Delay before pulses start. */
- private static final int PULSE_DELAY = 500;
-
- /** The target ripple being animated. */
- private final Ripple mTarget;
-
- /** When the ripple started appearing. */
- private long mEnterTime = -1;
-
- /** When the ripple started vanishing. */
- private long mExitTime = -1;
-
- /** When the ripple last transitioned between inside and outside touch. */
- private long mOutsideTime = -1;
-
- public RippleAnimator(Ripple target) {
- mTarget = target;
+ private final AnimatorListener mAnimationListener = new AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
}
- /**
- * Starts the enter animation.
- */
- public void enter() {
- mEnterTime = AnimationUtils.currentAnimationTimeMillis();
+ @Override
+ public void onAnimationRepeat(Animator animation) {
}
- /**
- * Starts the exit animation. If {@link #enter()} was called recently, the
- * animation may be postponed.
- */
- public void exit() {
- final long minTime = mEnterTime + EXIT_MIN_DELAY;
- mExitTime = Math.max(minTime, AnimationUtils.currentAnimationTimeMillis());
- }
-
- /**
- * Starts the outside transition animation.
- */
- public void outside() {
- mOutsideTime = AnimationUtils.currentAnimationTimeMillis();
- }
-
- /**
- * Returns whether this ripple is currently animating.
- */
- public boolean isRunning() {
- final long currentTime = AnimationUtils.currentAnimationTimeMillis();
- return mEnterTime >= 0 && mEnterTime <= currentTime
- && (mExitTime < 0 || currentTime <= mExitTime + EXIT_DURATION);
- }
-
- public void update() {
- // Track three states:
- // - Enter: touch begins, affects outer radius
- // - Outside: touch moves outside bounds, affects maximum outer radius
- // - Exit: touch ends, affects inner radius
- final long currentTime = AnimationUtils.currentAnimationTimeMillis();
- mTarget.mEnterState = mEnterTime < 0 ? 0 : INTERPOLATOR.getInterpolation(
- MathUtils.constrain((currentTime - mEnterTime) / (float) ENTER_DURATION, 0, 1));
- mTarget.mExitState = mExitTime < 0 ? 0 : INTERPOLATOR.getInterpolation(
- MathUtils.constrain((currentTime - mExitTime) / (float) EXIT_DURATION, 0, 1));
- mTarget.mOutsideState = mOutsideTime < 0 ? 1 : INTERPOLATOR.getInterpolation(
- MathUtils.constrain((currentTime - mOutsideTime) / (float) OUTSIDE_DURATION, 0, 1));
-
- // Pulse is a little more complicated.
- if (mTarget.mPulse) {
- final long pulseTime = (currentTime - mEnterTime - ENTER_DURATION - PULSE_DELAY);
- mTarget.mPulseState = pulseTime < 0 ? -1
- : (pulseTime % (PULSE_INTERVAL + PULSE_DURATION)) / (float) PULSE_DURATION;
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (animation == mExit) {
+ mExitFinished = true;
+ mOuterRadius = 0;
+ mInnerRadius = 0;
+ mAlphaMultiplier = 1;
}
}
- }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ }
+ };
}
diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
index 0e8831f..9000e5a 100644
--- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
+++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
@@ -27,8 +27,6 @@
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
-import android.graphics.drawable.Ripple.RippleAnimator;
-import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -40,7 +38,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.Arrays;
/**
* Documentation pending.
@@ -54,7 +51,6 @@
private static final int MAX_RIPPLES = 10;
private final Rect mTempRect = new Rect();
- private final Rect mPaddingRect = new Rect();
/** Current ripple effect bounds, used to constrain ripple effects. */
private final Rect mHotspotBounds = new Rect();
@@ -68,14 +64,11 @@
private final TouchFeedbackState mState;
/** Lazily-created map of touch hotspot IDs to ripples. */
- private SparseArray<Ripple> mTouchedRipples;
+ private SparseArray<Ripple> mRipples;
/** Lazily-created array of actively animating ripples. */
- private Ripple[] mActiveRipples;
- private int mActiveRipplesCount = 0;
-
- /** Lazily-created runnable for scheduling invalidation. */
- private Runnable mAnimationRunnable;
+ private Ripple[] mAnimatingRipples;
+ private int mAnimatingRipplesCount = 0;
/** Paint used to control appearance of ripples. */
private Paint mRipplePaint;
@@ -86,9 +79,6 @@
/** Target density of the display into which ripples are drawn. */
private float mDensity = 1.0f;
- /** Whether the animation runnable has been posted. */
- private boolean mAnimating;
-
/** Whether bounds are being overridden. */
private boolean mOverrideBounds;
@@ -154,28 +144,19 @@
private void onHotspotBoundsChange() {
final int x = mHotspotBounds.centerX();
final int y = mHotspotBounds.centerY();
- final int N = mActiveRipplesCount;
+ final int N = mAnimatingRipplesCount;
for (int i = 0; i < N; i++) {
if (mState.mPinned) {
- mActiveRipples[i].move(x, y);
+ mAnimatingRipples[i].move(x, y);
}
- mActiveRipples[i].onBoundsChanged();
+ mAnimatingRipples[i].onBoundsChanged();
}
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
if (!visible) {
- if (mTouchedRipples != null) {
- mTouchedRipples.clear();
- }
-
- if (mActiveRipplesCount > 0) {
- Arrays.fill(mActiveRipples, null);
- mActiveRipplesCount = 0;
- mAnimating = false;
- unscheduleSelf(mAnimationRunnable);
- }
+ clearHotspots();
}
return super.setVisible(visible, restart);
@@ -348,21 +329,18 @@
@Override
public void setHotspot(int id, float x, float y) {
- if (mTouchedRipples == null) {
- mTouchedRipples = new SparseArray<Ripple>();
- mActiveRipples = new Ripple[MAX_RIPPLES];
+ if (mRipples == null) {
+ mRipples = new SparseArray<Ripple>();
+ mAnimatingRipples = new Ripple[MAX_RIPPLES];
}
- if (mActiveRipplesCount >= MAX_RIPPLES) {
+ if (mAnimatingRipplesCount >= MAX_RIPPLES) {
Log.e(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
return;
}
- final Ripple ripple = mTouchedRipples.get(id);
+ final Ripple ripple = mRipples.get(id);
if (ripple == null) {
- final Rect padding = mPaddingRect;
- getPadding(padding);
-
final Rect bounds = mHotspotBounds;
if (mState.mPinned) {
x = bounds.exactCenterX();
@@ -371,11 +349,11 @@
// TODO: Clean this up in the API.
final boolean pulse = (id != R.attr.state_focused);
- final Ripple newRipple = new Ripple(bounds, padding, x, y, mDensity, pulse);
- newRipple.animate().enter();
+ final Ripple newRipple = new Ripple(this, bounds, mDensity, pulse);
+ newRipple.move(x, y);
- mActiveRipples[mActiveRipplesCount++] = newRipple;
- mTouchedRipples.put(id, newRipple);
+ mAnimatingRipples[mAnimatingRipplesCount++] = newRipple;
+ mRipples.put(id, newRipple);
} else if (mState.mPinned) {
final Rect bounds = mHotspotBounds;
x = bounds.exactCenterX();
@@ -384,41 +362,30 @@
} else {
ripple.move(x, y);
}
-
- scheduleAnimation();
}
@Override
public void removeHotspot(int id) {
- if (mTouchedRipples == null) {
+ if (mRipples == null) {
return;
}
- final Ripple ripple = mTouchedRipples.get(id);
+ final Ripple ripple = mRipples.get(id);
if (ripple != null) {
- ripple.animate().exit();
+ ripple.exit();
- mTouchedRipples.remove(id);
- scheduleAnimation();
+ mRipples.remove(id);
}
}
@Override
public void clearHotspots() {
- if (mTouchedRipples == null) {
+ if (mRipples == null) {
return;
}
- final int n = mTouchedRipples.size();
- for (int i = 0; i < n; i++) {
- // TODO: Use a fast exit, maybe just fade out?
- mTouchedRipples.valueAt(i).animate().exit();
- }
-
- if (n > 0) {
- mTouchedRipples.clear();
- scheduleAnimation();
- }
+ mRipples.clear();
+ invalidateSelf();
}
/**
@@ -431,30 +398,6 @@
onHotspotBoundsChange();
}
- /**
- * Schedules the next animation, if necessary.
- */
- private void scheduleAnimation() {
- if (mActiveRipplesCount == 0) {
- mAnimating = false;
- } else if (!mAnimating) {
- mAnimating = true;
-
- if (mAnimationRunnable == null) {
- mAnimationRunnable = new Runnable() {
- @Override
- public void run() {
- mAnimating = false;
- scheduleAnimation();
- invalidateSelf();
- }
- };
- }
-
- scheduleSelf(mAnimationRunnable, SystemClock.uptimeMillis() + 1000 / 60);
- }
- }
-
@Override
public void draw(Canvas canvas) {
final int N = mLayerState.mNum;
@@ -501,12 +444,12 @@
}
private int drawRippleLayer(Canvas canvas, Rect bounds, boolean maskOnly) {
- final int ripplesCount = mActiveRipplesCount;
- if (ripplesCount == 0) {
+ final int count = mAnimatingRipplesCount;
+ if (count == 0) {
return -1;
}
- final Ripple[] activeRipples = mActiveRipples;
+ final Ripple[] ripples = mAnimatingRipples;
final boolean projected = isProjected();
final Rect layerBounds = projected ? getDirtyBounds() : bounds;
@@ -529,17 +472,15 @@
boolean drewRipples = false;
int restoreToCount = -1;
- int activeRipplesCount = 0;
+ int animatingCount = 0;
- // Draw ripples.
- for (int i = 0; i < ripplesCount; i++) {
- final Ripple ripple = activeRipples[i];
- final RippleAnimator animator = ripple.animate();
- animator.update();
+ // Draw ripples and update the animating ripples array.
+ for (int i = 0; i < count; i++) {
+ final Ripple ripple = ripples[i];
- // Mark and skip inactive ripples.
- if (!animator.isRunning()) {
- activeRipples[i] = null;
+ // Mark and skip finished ripples.
+ if (ripple.isFinished()) {
+ ripples[i] = null;
continue;
}
@@ -565,11 +506,11 @@
drewRipples |= ripple.draw(canvas, ripplePaint);
- activeRipples[activeRipplesCount] = activeRipples[i];
- activeRipplesCount++;
+ ripples[animatingCount] = ripples[i];
+ animatingCount++;
}
- mActiveRipplesCount = activeRipplesCount;
+ mAnimatingRipplesCount = animatingCount;
// If we created a layer with no content, merge it immediately.
if (restoreToCount >= 0 && !drewRipples) {
@@ -596,8 +537,8 @@
drawingBounds.setEmpty();
final Rect rippleBounds = mTempRect;
- final Ripple[] activeRipples = mActiveRipples;
- final int N = mActiveRipplesCount;
+ final Ripple[] activeRipples = mAnimatingRipples;
+ final int N = mAnimatingRipplesCount;
for (int i = 0; i < N; i++) {
activeRipples[i].getBounds(rippleBounds);
drawingBounds.union(rippleBounds);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
index 1bc97a0..b09b462 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ZenModeViewAdapter.java
@@ -58,7 +58,7 @@
mNoMan = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
try {
- mNoMan.requestZenModeConditions(mListener, true /*requested*/);
+ mNoMan.requestZenModeConditions(mListener, Condition.FLAG_RELEVANT_NOW);
} catch (RemoteException e) {
// noop
}
@@ -98,7 +98,7 @@
@Override
public void dispose() {
try {
- mNoMan.requestZenModeConditions(mListener, false /*requested*/);
+ mNoMan.requestZenModeConditions(mListener, 0 /*none*/);
} catch (RemoteException e) {
// noop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index a364b416..4c0962e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -106,13 +106,15 @@
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
private boolean mChildHierarchyDirty;
private boolean mIsExpanded = true;
- private ViewTreeObserver.OnPreDrawListener mAfterLayoutPreDrawListener
+ private boolean mChildrenNeedUpdate;
+ private ViewTreeObserver.OnPreDrawListener mPreDrawListener
= new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
- updateScrollPositionIfNecessary();
- updateChildren();
- getViewTreeObserver().removeOnPreDrawListener(this);
+ if (mChildrenNeedUpdate) {
+ updateChildren();
+ mChildrenNeedUpdate = false;
+ }
return true;
}
};
@@ -179,6 +181,7 @@
mPaddingBetweenElements = context.getResources()
.getDimensionPixelSize(R.dimen.notification_padding);
mStackScrollAlgorithm = new StackScrollAlgorithm(context);
+ getViewTreeObserver().addOnPreDrawListener(mPreDrawListener);
}
@Override
@@ -206,7 +209,8 @@
}
setMaxLayoutHeight(getHeight() - mEmptyMarginBottom);
updateContentHeight();
- getViewTreeObserver().addOnPreDrawListener(mAfterLayoutPreDrawListener);
+ updateScrollPositionIfNecessary();
+ requestChildrenUpdate();
}
public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
@@ -265,14 +269,16 @@
mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState);
if (!isCurrentlyAnimating() && !mChildHierarchyDirty) {
applyCurrentState();
- if (mListener != null) {
- mListener.onChildLocationsChanged(this);
- }
} else {
- startAnimationToState(mCurrentStackScrollState);
+ startAnimationToState();
}
}
+ private void requestChildrenUpdate() {
+ mChildrenNeedUpdate = true;
+ invalidate();
+ }
+
private boolean isCurrentlyAnimating() {
return mStateAnimator.isRunning();
}
@@ -293,7 +299,7 @@
mTopPadding = topPadding;
updateAlgorithmHeightAndPadding();
updateContentHeight();
- updateChildren();
+ requestChildrenUpdate();
}
}
@@ -328,7 +334,7 @@
if (stackHeight != mCurrentStackHeight) {
mCurrentStackHeight = stackHeight;
updateAlgorithmHeightAndPadding();
- updateChildren();
+ requestChildrenUpdate();
}
}
@@ -379,7 +385,7 @@
public View getChildAtRawPosition(float touchX, float touchY) {
int[] location = new int[2];
getLocationOnScreen(location);
- return getChildAtPosition(touchX - location[0],touchY - location[1]);
+ return getChildAtPosition(touchX - location[0], touchY - location[1]);
}
public View getChildAtPosition(float touchX, float touchY) {
@@ -662,19 +668,13 @@
}
}
- public void customScrollBy(int y) {
- mOwnScrollY += y;
- updateChildren();
- }
-
- public void customScrollTo(int y) {
+ private void customScrollTo(int y) {
mOwnScrollY = y;
updateChildren();
}
@Override
- protected void onOverScrolled(int scrollX, int scrollY,
- boolean clampedX, boolean clampedY) {
+ protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
// Treat animating scrolls differently; see #computeScroll() for why.
if (!mScroller.isFinished()) {
final int oldX = mScrollX;
@@ -831,9 +831,13 @@
updateScrollStateForRemovedChild(child);
if (mIsExpanded) {
- // Generate Animations
- mChildrenToRemoveAnimated.add(child);
- mChildHierarchyDirty = true;
+ if (!mChildrenToAddAnimated.contains(child)) {
+ // Generate Animations
+ mChildrenToRemoveAnimated.add(child);
+ mChildHierarchyDirty = true;
+ } else {
+ mChildrenToAddAnimated.remove(child);
+ }
}
}
@@ -905,12 +909,16 @@
}
}
- private void startAnimationToState(StackScrollState finalState) {
+ private void startAnimationToState() {
if (mChildHierarchyDirty) {
generateChildHierarchyEvents();
mChildHierarchyDirty = false;
}
- mStateAnimator.startAnimationForEvents(mAnimationEvents, finalState);
+ if (!mAnimationEvents.isEmpty()) {
+ mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState);
+ } else {
+ applyCurrentState();
+ }
}
private void generateChildHierarchyEvents() {
@@ -1110,7 +1118,7 @@
if (mOnHeightChangedListener != null) {
mOnHeightChangedListener.onHeightChanged(view);
}
- updateChildren();
+ requestChildrenUpdate();
}
}
@@ -1120,7 +1128,7 @@
}
public void onChildAnimationFinished() {
- applyCurrentState();
+ requestChildrenUpdate();
mAnimationEvents.clear();
}
@@ -1128,6 +1136,9 @@
mListenForHeightChanges = false;
mCurrentStackScrollState.apply();
mListenForHeightChanges = true;
+ if (mListener != null) {
+ mListener.onChildLocationsChanged(this);
+ }
}
/**
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 0550dd4..50e2a2e 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -3573,14 +3573,14 @@
});
swipeDismiss.setOnSwipeProgressChangedListener(
new SwipeDismissLayout.OnSwipeProgressChangedListener() {
+ private static final float ALPHA_DECREASE = 0.5f;
private boolean mIsTranslucent = false;
-
@Override
public void onSwipeProgressChanged(
SwipeDismissLayout layout, float progress, float translate) {
WindowManager.LayoutParams newParams = getAttributes();
newParams.x = (int) translate;
- newParams.alpha = 1 - progress;
+ newParams.alpha = 1 - (progress * ALPHA_DECREASE);
setAttributes(newParams);
int flags = 0;
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 5567944..d074565 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -16,6 +16,7 @@
package com.android.server.notification;
+import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
@@ -28,31 +29,31 @@
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
+import android.service.notification.ZenModeConfig;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.R;
-import libcore.util.Objects;
-
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
public class ConditionProviders extends ManagedServices {
+ private static final Condition[] NO_CONDITIONS = new Condition[0];
private final ZenModeHelper mZenModeHelper;
private final ArrayMap<IBinder, IConditionListener> mListeners
= new ArrayMap<IBinder, IConditionListener>();
- private final ArrayMap<Uri, ManagedServiceInfo> mConditions
- = new ArrayMap<Uri, ManagedServiceInfo>();
-
- private Uri mCurrentConditionId;
+ private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
public ConditionProviders(Context context, Handler handler,
UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
super(context, handler, new Object(), userProfiles);
mZenModeHelper = zenModeHelper;
mZenModeHelper.addCallback(new ZenModeHelperCallback());
+ loadZenConfig();
}
@Override
@@ -71,20 +72,13 @@
public void dump(PrintWriter pw) {
super.dump(pw);
synchronized(mMutex) {
- pw.print(" mCurrentConditionId="); pw.println(mCurrentConditionId);
pw.print(" mListeners("); pw.print(mListeners.size()); pw.println("):");
for (int i = 0; i < mListeners.size(); i++) {
pw.print(" "); pw.println(mListeners.keyAt(i));
}
- pw.print(" mConditions("); pw.print(mConditions.size()); pw.println("):");
- for (int i = 0; i < mConditions.size(); i++) {
- pw.print(" "); pw.print(mConditions.keyAt(i));
- final ManagedServiceInfo info = mConditions.valueAt(i);
- pw.print(" -> "); pw.print(info.component);
- if (!mServices.contains(info)) {
- pw.print(" (orphan)");
- }
- pw.println();
+ pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):");
+ for (int i = 0; i < mRecords.size(); i++) {
+ pw.print(" "); pw.println(mRecords.get(i));
}
}
}
@@ -95,29 +89,49 @@
}
@Override
- protected void onServiceAdded(IInterface service) {
- Slog.d(TAG, "onServiceAdded " + service);
- final IConditionProvider provider = (IConditionProvider) service;
+ protected void onServiceAdded(ManagedServiceInfo info) {
+ Slog.d(TAG, "onServiceAdded " + info);
+ final IConditionProvider provider = provider(info);
try {
provider.onConnected();
} catch (RemoteException e) {
// we tried
}
+ synchronized (mMutex) {
+ final int N = mRecords.size();
+ for(int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ if (!r.component.equals(info.component)) continue;
+ r.info = info;
+ // if automatic, auto-subscribe
+ if (r.isAutomatic) {
+ try {
+ final Uri id = r.id;
+ if (DEBUG) Slog.d(TAG, "Auto-subscribing to configured condition " + id);
+ provider.onSubscribe(id);
+ } catch (RemoteException e) {
+ // we tried
+ }
+ }
+ }
+ }
}
@Override
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
if (removed == null) return;
- if (mCurrentConditionId != null) {
- if (removed.equals(mConditions.get(mCurrentConditionId))) {
- mCurrentConditionId = null;
+ for (int i = mRecords.size() - 1; i >= 0; i--) {
+ final ConditionRecord r = mRecords.get(i);
+ if (!r.component.equals(removed.component)) continue;
+ if (r.isManual) {
+ // removing the current manual condition, exit zen
mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
}
- }
- for (int i = mConditions.size() - 1; i >= 0; i--) {
- if (removed.equals(mConditions.valueAt(i))) {
- mConditions.removeAt(i);
+ if (r.isAutomatic) {
+ // removing an automatic condition, exit zen
+ mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF);
}
+ mRecords.remove(i);
}
}
@@ -127,14 +141,15 @@
}
}
- public void requestZenModeConditions(IConditionListener callback, boolean requested) {
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
synchronized(mMutex) {
if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
- + " requested=" + requested);
+ + " relevance=" + Condition.relevanceToString(relevance));
if (callback == null) return;
- if (requested) {
+ relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
+ if (relevance != 0) {
mListeners.put(callback.asBinder(), callback);
- requestConditionsLocked(Condition.FLAG_RELEVANT_NOW);
+ requestConditionsLocked(relevance);
} else {
mListeners.remove(callback.asBinder());
if (mListeners.isEmpty()) {
@@ -144,25 +159,51 @@
}
}
+ private Condition[] validateConditions(String pkg, Condition[] conditions) {
+ if (conditions == null || conditions.length == 0) return null;
+ final int N = conditions.length;
+ final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
+ for (int i = 0; i < N; i++) {
+ final Uri id = conditions[i].id;
+ if (!Condition.isValidId(id, pkg)) {
+ Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
+ continue;
+ }
+ if (valid.containsKey(id)) {
+ Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
+ continue;
+ }
+ valid.put(id, conditions[i]);
+ }
+ if (valid.size() == 0) return null;
+ if (valid.size() == N) return conditions;
+ final Condition[] rt = new Condition[valid.size()];
+ for (int i = 0; i < rt.length; i++) {
+ rt[i] = valid.valueAt(i);
+ }
+ return rt;
+ }
+
+ private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
+ final int N = mRecords.size();
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ if (r.id.equals(id) && r.component.equals(component)) {
+ return r;
+ }
+ }
+ final ConditionRecord r = new ConditionRecord(id, component);
+ mRecords.add(r);
+ return r;
+ }
+
public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
synchronized(mMutex) {
if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
+ (conditions == null ? null : Arrays.asList(conditions)));
+ conditions = validateConditions(pkg, conditions);
if (conditions == null || conditions.length == 0) return;
final int N = conditions.length;
- boolean valid = true;
- for (int i = 0; i < N; i++) {
- final Uri id = conditions[i].id;
- if (!Condition.isValidId(id, pkg)) {
- Slog.w(TAG, "Ignoring conditions from " + pkg + " for invalid id: " + id);
- valid = false;
- }
- }
- if (!valid) return;
-
- for (int i = 0; i < N; i++) {
- mConditions.put(conditions[i].id, info);
- }
for (IConditionListener listener : mListeners.values()) {
try {
listener.onConditionsReceived(conditions);
@@ -170,62 +211,154 @@
Slog.w(TAG, "Error sending conditions to listener " + listener, e);
}
}
- if (mCurrentConditionId != null) {
- for (int i = 0; i < N; i++) {
- final Condition c = conditions[i];
- if (!c.id.equals(mCurrentConditionId)) continue;
- if (c.state == Condition.STATE_TRUE || c.state == Condition.STATE_ERROR) {
- triggerExitLocked(c.state == Condition.STATE_ERROR);
- return;
+ for (int i = 0; i < N; i++) {
+ final Condition c = conditions[i];
+ final ConditionRecord r = getRecordLocked(c.id, info.component);
+ r.info = info;
+ r.condition = c;
+ // if manual, exit zen if false (or failed)
+ if (r.isManual) {
+ if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
+ final boolean failed = c.state == Condition.STATE_ERROR;
+ if (failed) {
+ Slog.w(TAG, "Exit zen: manual condition failed: " + c);
+ } else if (DEBUG) {
+ Slog.d(TAG, "Exit zen: manual condition false: " + c);
+ }
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
+ unsubscribeLocked(r);
+ r.isManual = false;
+ }
+ }
+ // if automatic, exit zen if false (or failed), enter zen if true
+ if (r.isAutomatic) {
+ if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
+ final boolean failed = c.state == Condition.STATE_ERROR;
+ if (failed) {
+ Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
+ } else if (DEBUG) {
+ Slog.d(TAG, "Exit zen: automatic condition false: " + c);
+ }
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
+ } else if (c.state == Condition.STATE_TRUE) {
+ Slog.d(TAG, "Enter zen: automatic condition true: " + c);
+ mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_ON);
}
}
}
}
}
- private void triggerExitLocked(boolean error) {
- if (error) {
- Slog.w(TAG, "Zen mode exit condition failed");
- } else if (DEBUG) {
- Slog.d(TAG, "Zen mode exit condition triggered");
- }
- mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF);
- unsubscribeLocked(mCurrentConditionId);
- mCurrentConditionId = null;
- }
-
public void setZenModeCondition(Uri conditionId) {
+ if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
synchronized(mMutex) {
- if (DEBUG) Slog.d(TAG, "setZenModeCondition " + conditionId);
- if (Objects.equal(mCurrentConditionId, conditionId)) return;
-
- if (mCurrentConditionId != null) {
- unsubscribeLocked(mCurrentConditionId);
- }
- if (conditionId != null) {
- final ManagedServiceInfo info = mConditions.get(conditionId);
- final IConditionProvider provider = provider(info);
- if (provider == null) return;
- try {
- provider.onSubscribe(conditionId);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error subscribing to " + conditionId
- + " from " + info.component, e);
+ final int N = mRecords.size();
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ final boolean idEqual = r.id.equals(conditionId);
+ if (r.isManual && !idEqual) {
+ // was previous manual condition, unsubscribe
+ unsubscribeLocked(r);
+ r.isManual = false;
+ } else if (idEqual && !r.isManual) {
+ // is new manual condition, subscribe
+ subscribeLocked(r);
+ r.isManual = true;
}
}
- mCurrentConditionId = conditionId;
}
}
- private void unsubscribeLocked(Uri conditionId) {
- final ManagedServiceInfo info = mConditions.get(mCurrentConditionId);
- final IConditionProvider provider = provider(info);
- if (provider == null) return;
- try {
- provider.onUnsubscribe(conditionId);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error unsubscribing to " + conditionId + " from " + info.component, e);
+ private void subscribeLocked(ConditionRecord r) {
+ if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
+ final IConditionProvider provider = provider(r);
+ if (provider == null) {
+ Slog.w(TAG, "subscribeLocked: no provider");
+ return;
}
+ try {
+ provider.onSubscribe(r.id);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error subscribing to " + r, e);
+ }
+ }
+
+ private static <T> ArraySet<T> safeSet(T... items) {
+ final ArraySet<T> rt = new ArraySet<T>();
+ if (items == null || items.length == 0) return rt;
+ final int N = items.length;
+ for (int i = 0; i < N; i++) {
+ final T item = items[i];
+ if (item != null) {
+ rt.add(item);
+ }
+ }
+ return rt;
+ }
+
+ public void setAutomaticZenModeConditions(Uri[] conditionIds) {
+ setAutomaticZenModeConditions(conditionIds, true /*save*/);
+ }
+
+ private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
+ if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
+ + (conditionIds == null ? null : Arrays.asList(conditionIds)));
+ synchronized(mMutex) {
+ final ArraySet<Uri> newIds = safeSet(conditionIds);
+ final int N = mRecords.size();
+ boolean changed = false;
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ final boolean automatic = newIds.contains(r.id);
+ if (!r.isAutomatic && automatic) {
+ // subscribe to new automatic
+ subscribeLocked(r);
+ r.isAutomatic = true;
+ changed = true;
+ } else if (r.isAutomatic && !automatic) {
+ // unsubscribe from old automatic
+ unsubscribeLocked(r);
+ r.isAutomatic = false;
+ changed = true;
+ }
+ }
+ if (save && changed) {
+ saveZenConfigLocked();
+ }
+ }
+ }
+
+ public Condition[] getAutomaticZenModeConditions() {
+ synchronized(mMutex) {
+ final int N = mRecords.size();
+ ArrayList<Condition> rt = null;
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ if (r.isAutomatic && r.condition != null) {
+ if (rt == null) rt = new ArrayList<Condition>();
+ rt.add(r.condition);
+ }
+ }
+ return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
+ }
+ }
+
+ private void unsubscribeLocked(ConditionRecord r) {
+ if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
+ final IConditionProvider provider = provider(r);
+ if (provider == null) {
+ Slog.w(TAG, "unsubscribeLocked: no provider");
+ return;
+ }
+ try {
+ provider.onUnsubscribe(r.id);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error unsubscribing to " + r, e);
+ }
+ }
+
+ private static IConditionProvider provider(ConditionRecord r) {
+ return r == null ? null : provider(r.info);
}
private static IConditionProvider provider(ManagedServiceInfo info) {
@@ -244,20 +377,99 @@
}
}
+ private void loadZenConfig() {
+ final ZenModeConfig config = mZenModeHelper.getConfig();
+ if (config == null) {
+ if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
+ return;
+ }
+ synchronized (mMutex) {
+ if (config.conditionComponents == null || config.conditionIds == null
+ || config.conditionComponents.length != config.conditionIds.length) {
+ if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
+ setAutomaticZenModeConditions(null, false /*save*/);
+ return;
+ }
+ final ArraySet<Uri> newIds = new ArraySet<Uri>();
+ final int N = config.conditionComponents.length;
+ for (int i = 0; i < N; i++) {
+ final ComponentName component = config.conditionComponents[i];
+ final Uri id = config.conditionIds[i];
+ if (component != null && id != null) {
+ getRecordLocked(id, component); // ensure record exists
+ newIds.add(id);
+ }
+ }
+ if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
+ setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
+ }
+ }
+
+ private void saveZenConfigLocked() {
+ ZenModeConfig config = mZenModeHelper.getConfig();
+ if (config == null) return;
+ config = config.copy();
+ final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
+ final int automaticN = mRecords.size();
+ for (int i = 0; i < automaticN; i++) {
+ final ConditionRecord r = mRecords.get(i);
+ if (r.isAutomatic) {
+ automatic.add(r);
+ }
+ }
+ if (automatic.isEmpty()) {
+ config.conditionComponents = null;
+ config.conditionIds = null;
+ } else {
+ final int N = automatic.size();
+ config.conditionComponents = new ComponentName[N];
+ config.conditionIds = new Uri[N];
+ for (int i = 0; i < N; i++) {
+ final ConditionRecord r = automatic.get(i);
+ config.conditionComponents[i] = r.component;
+ config.conditionIds[i] = r.id;
+ }
+ }
+ if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
+ mZenModeHelper.setConfig(config);
+ }
+
private class ZenModeHelperCallback extends ZenModeHelper.Callback {
@Override
+ void onConfigChanged() {
+ loadZenConfig();
+ }
+
+ @Override
void onZenModeChanged() {
final int mode = mZenModeHelper.getZenMode();
if (mode == Global.ZEN_MODE_OFF) {
- synchronized (mMutex) {
- if (mCurrentConditionId != null) {
- if (DEBUG) Slog.d(TAG, "Zen mode off, forcing unsubscribe from "
- + mCurrentConditionId);
- unsubscribeLocked(mCurrentConditionId);
- mCurrentConditionId = null;
- }
- }
+ // ensure any manual condition is cleared
+ setZenModeCondition(null);
}
}
}
+
+ private static class ConditionRecord {
+ public final Uri id;
+ public final ComponentName component;
+ public Condition condition;
+ public ManagedServiceInfo info;
+ public boolean isAutomatic;
+ public boolean isManual;
+
+ private ConditionRecord(Uri id, ComponentName component) {
+ this.id = id;
+ this.component = component;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
+ .append(id).append(",component=").append(component);
+ if (isAutomatic) sb.append(",automatic");
+ if (isManual) sb.append(",manual");
+ return sb.append(']').toString();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0621f58..d34b09c 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -101,7 +101,7 @@
abstract protected IInterface asInterface(IBinder binder);
- abstract protected void onServiceAdded(IInterface service);
+ abstract protected void onServiceAdded(ManagedServiceInfo info);
protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
@@ -368,11 +368,12 @@
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
boolean added = false;
+ ManagedServiceInfo info = null;
synchronized (mMutex) {
mServicesBinding.remove(servicesBindingTag);
try {
mService = asInterface(binder);
- ManagedServiceInfo info = newServiceInfo(mService, name,
+ info = newServiceInfo(mService, name,
userid, false /*isSystem*/, this, targetSdkVersion);
binder.linkToDeath(info, 0);
added = mServices.add(info);
@@ -381,7 +382,7 @@
}
}
if (added) {
- onServiceAdded(mService);
+ onServiceAdded(info);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6e4eb565..2e52983 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1355,9 +1355,9 @@
}
@Override
- public void requestZenModeConditions(IConditionListener callback, boolean requested) {
+ public void requestZenModeConditions(IConditionListener callback, int relevance) {
enforceSystemOrSystemUI("INotificationManager.requestZenModeConditions");
- mConditionProviders.requestZenModeConditions(callback, requested);
+ mConditionProviders.requestZenModeConditions(callback, relevance);
}
@Override
@@ -1366,6 +1366,18 @@
mConditionProviders.setZenModeCondition(conditionId);
}
+ @Override
+ public void setAutomaticZenModeConditions(Uri[] conditionIds) {
+ enforceSystemOrSystemUI("INotificationManager.setAutomaticZenModeConditions");
+ mConditionProviders.setAutomaticZenModeConditions(conditionIds);
+ }
+
+ @Override
+ public Condition[] getAutomaticZenModeConditions() {
+ enforceSystemOrSystemUI("INotificationManager.getAutomaticZenModeConditions");
+ return mConditionProviders.getAutomaticZenModeConditions();
+ }
+
private void enforceSystemOrSystemUI(String message) {
if (isCallerSystem()) return;
getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
@@ -2320,8 +2332,8 @@
}
@Override
- public void onServiceAdded(IInterface service) {
- final INotificationListener listener = (INotificationListener) service;
+ public void onServiceAdded(ManagedServiceInfo info) {
+ final INotificationListener listener = (INotificationListener) info.service;
final String[] keys = getActiveNotificationKeysFromListener(listener);
try {
listener.onListenerConnected(keys);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 606fcb4..1ee390f 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -797,6 +797,8 @@
return TelephonyManager.NETWORK_TYPE_LTE;
case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
return TelephonyManager.NETWORK_TYPE_HSPAP;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_GSM:
+ return TelephonyManager.NETWORK_TYPE_GSM;
default:
return TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 407a8d1..df972d5 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -911,6 +911,8 @@
public static final int NETWORK_TYPE_EHRPD = 14;
/** Current network is HSPA+ */
public static final int NETWORK_TYPE_HSPAP = 15;
+ /** Current network is GSM {@hide} */
+ public static final int NETWORK_TYPE_GSM = 16;
/**
* @return the NETWORK_TYPE_xxxx for current data connection.
@@ -1002,6 +1004,7 @@
public static int getNetworkClass(int networkType) {
switch (networkType) {
case NETWORK_TYPE_GPRS:
+ case NETWORK_TYPE_GSM:
case NETWORK_TYPE_EDGE:
case NETWORK_TYPE_CDMA:
case NETWORK_TYPE_1xRTT:
@@ -1068,6 +1071,8 @@
return "iDEN";
case NETWORK_TYPE_HSPAP:
return "HSPA+";
+ case NETWORK_TYPE_GSM:
+ return "GSM";
default:
return "UNKNOWN";
}