Merge "Begin switch over to task based history."
diff --git a/Android.mk b/Android.mk
index c8576a0..b9cd7bf 100644
--- a/Android.mk
+++ b/Android.mk
@@ -69,6 +69,7 @@
core/java/android/app/IAlarmManager.aidl \
core/java/android/app/IBackupAgent.aidl \
core/java/android/app/IInstrumentationWatcher.aidl \
+ core/java/android/app/INotificationListener.aidl \
core/java/android/app/INotificationManager.aidl \
core/java/android/app/IProcessObserver.aidl \
core/java/android/app/ISearchManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index a6119b6..bbf9302 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22969,9 +22969,14 @@
public class EasyEditSpan implements android.text.ParcelableSpan {
ctor public EasyEditSpan();
+ ctor public EasyEditSpan(android.app.PendingIntent);
+ ctor public EasyEditSpan(android.os.Parcel);
method public int describeContents();
method public int getSpanTypeId();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final java.lang.String EXTRA_TEXT_CHANGED_TYPE = "android.text.style.EXTRA_TEXT_CHANGED_TYPE";
+ field public static final int TEXT_DELETED = 1; // 0x1
+ field public static final int TEXT_MODIFIED = 2; // 0x2
}
public class ForegroundColorSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan android.text.style.UpdateAppearance {
diff --git a/core/java/android/app/INotificationListener.aidl b/core/java/android/app/INotificationListener.aidl
new file mode 100644
index 0000000..f010a2a
--- /dev/null
+++ b/core/java/android/app/INotificationListener.aidl
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2013, 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;
+
+import com.android.internal.statusbar.StatusBarNotification;
+
+/** @hide */
+oneway interface INotificationListener
+{
+ void onNotificationPosted(in StatusBarNotification notification);
+ void onNotificationRemoved(in StatusBarNotification notification);
+}
\ No newline at end of file
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 1f4c81d..14bcc0d 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -17,6 +17,7 @@
package android.app;
+import android.app.INotificationListener;
import android.app.ITransientNotification;
import android.app.Notification;
import android.content.Intent;
@@ -39,5 +40,8 @@
StatusBarNotification[] getActiveNotifications(String callingPkg);
StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
+
+ void registerListener(in INotificationListener listener, int userid);
+ void unregisterListener(in INotificationListener listener, int userid);
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 1508d10..2ab9bf8 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -757,7 +757,7 @@
break;
case EASY_EDIT_SPAN:
- readSpan(p, sp, new EasyEditSpan());
+ readSpan(p, sp, new EasyEditSpan(p));
break;
case LOCALE_SPAN:
diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java
index 2feb719..03b4f60 100644
--- a/core/java/android/text/style/EasyEditSpan.java
+++ b/core/java/android/text/style/EasyEditSpan.java
@@ -16,6 +16,7 @@
package android.text.style;
+import android.app.PendingIntent;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextUtils;
@@ -25,12 +26,62 @@
* Provides an easy way to edit a portion of text.
* <p>
* The {@link TextView} uses this span to allow the user to delete a chuck of text in one click.
- * the text. {@link TextView} removes this span as soon as the text is edited, or the cursor moves.
+ * <p>
+ * {@link TextView} removes the span when the user deletes the whole text or modifies it.
+ * <p>
+ * This span can be also used to receive notification when the user deletes or modifies the text;
*/
public class EasyEditSpan implements ParcelableSpan {
+ /**
+ * The extra key field in the pending intent that describes how the text changed.
+ *
+ * @see #TEXT_DELETED
+ * @see #TEXT_MODIFIED
+ * @see #getPendingIntent()
+ */
+ public static final String EXTRA_TEXT_CHANGED_TYPE =
+ "android.text.style.EXTRA_TEXT_CHANGED_TYPE";
+
+ /**
+ * The value of {@link #EXTRA_TEXT_CHANGED_TYPE} when the text wrapped by this span is deleted.
+ */
+ public static final int TEXT_DELETED = 1;
+
+ /**
+ * The value of {@link #EXTRA_TEXT_CHANGED_TYPE} when the text wrapped by this span is modified.
+ */
+ public static final int TEXT_MODIFIED = 2;
+
+ private final PendingIntent mPendingIntent;
+
+ private boolean mDeleteEnabled;
+
+ /**
+ * Creates the span. No intent is sent when the wrapped text is modified or
+ * deleted.
+ */
public EasyEditSpan() {
- // Empty
+ mPendingIntent = null;
+ mDeleteEnabled = true;
+ }
+
+ /**
+ * @param pendingIntent The intent will be sent when the wrapped text is deleted or modified.
+ * When the pending intent is sent, {@link #EXTRA_TEXT_CHANGED_TYPE} is
+ * added in the intent to describe how the text changed.
+ */
+ public EasyEditSpan(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ mDeleteEnabled = true;
+ }
+
+ /**
+ * Constructor called from {@link TextUtils} to restore the span.
+ */
+ public EasyEditSpan(Parcel source) {
+ mPendingIntent = source.readParcelable(null);
+ mDeleteEnabled = (source.readByte() == 1);
}
@Override
@@ -40,11 +91,39 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- // Empty
+ dest.writeParcelable(mPendingIntent, 0);
+ dest.writeByte((byte) (mDeleteEnabled ? 1 : 0));
}
@Override
public int getSpanTypeId() {
return TextUtils.EASY_EDIT_SPAN;
}
+
+ /**
+ * @return True if the {@link TextView} should offer the ability to delete the text.
+ *
+ * @hide
+ */
+ public boolean isDeleteEnabled() {
+ return mDeleteEnabled;
+ }
+
+ /**
+ * Enables or disables the deletion of the text.
+ *
+ * @hide
+ */
+ public void setDeleteEnabled(boolean value) {
+ mDeleteEnabled = value;
+ }
+
+ /**
+ * @return the pending intent to send when the wrapped text is deleted or modified.
+ *
+ * @hide
+ */
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 862e2c8..dc305a5 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -20,6 +20,8 @@
import com.android.internal.widget.EditableInputConnection;
import android.R;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
import android.content.ClipData;
import android.content.ClipData.Item;
import android.content.Context;
@@ -1890,10 +1892,23 @@
// Make sure there is only at most one EasyEditSpan in the text
if (mPopupWindow.mEasyEditSpan != null) {
- text.removeSpan(mPopupWindow.mEasyEditSpan);
+ mPopupWindow.mEasyEditSpan.setDeleteEnabled(false);
}
mPopupWindow.setEasyEditSpan((EasyEditSpan) span);
+ mPopupWindow.setOnDeleteListener(new EasyEditDeleteListener() {
+ @Override
+ public void onDeleteClick(EasyEditSpan span) {
+ Editable editable = (Editable) mTextView.getText();
+ int start = editable.getSpanStart(span);
+ int end = editable.getSpanEnd(span);
+ if (start >= 0 && end >= 0) {
+ sendNotification(EasyEditSpan.TEXT_DELETED, span);
+ mTextView.deleteText_internal(start, end);
+ }
+ editable.removeSpan(span);
+ }
+ });
if (mTextView.getWindowVisibility() != View.VISIBLE) {
// The window is not visible yet, ignore the text change.
@@ -1927,8 +1942,10 @@
@Override
public void onSpanChanged(Spannable text, Object span, int previousStart, int previousEnd,
int newStart, int newEnd) {
- if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
- text.removeSpan(mPopupWindow.mEasyEditSpan);
+ if (mPopupWindow != null && span instanceof EasyEditSpan) {
+ EasyEditSpan easyEditSpan = (EasyEditSpan) span;
+ sendNotification(EasyEditSpan.TEXT_MODIFIED, easyEditSpan);
+ text.removeSpan(easyEditSpan);
}
}
@@ -1938,6 +1955,31 @@
mTextView.removeCallbacks(mHidePopup);
}
}
+
+ private void sendNotification(int textChangedType, EasyEditSpan span) {
+ try {
+ PendingIntent pendingIntent = span.getPendingIntent();
+ if (pendingIntent != null) {
+ Intent intent = new Intent();
+ intent.putExtra(EasyEditSpan.EXTRA_TEXT_CHANGED_TYPE, textChangedType);
+ pendingIntent.send(mTextView.getContext(), 0, intent);
+ }
+ } catch (CanceledException e) {
+ // This should not happen, as we should try to send the intent only once.
+ Log.w(TAG, "PendingIntent for notification cannot be sent", e);
+ }
+ }
+ }
+
+ /**
+ * Listens for the delete event triggered by {@link EasyEditPopupWindow}.
+ */
+ private interface EasyEditDeleteListener {
+
+ /**
+ * Clicks the delete pop-up.
+ */
+ void onDeleteClick(EasyEditSpan span);
}
/**
@@ -1950,6 +1992,7 @@
com.android.internal.R.layout.text_edit_action_popup_text;
private TextView mDeleteTextView;
private EasyEditSpan mEasyEditSpan;
+ private EasyEditDeleteListener mOnDeleteListener;
@Override
protected void createPopupWindow() {
@@ -1984,19 +2027,29 @@
mEasyEditSpan = easyEditSpan;
}
+ private void setOnDeleteListener(EasyEditDeleteListener listener) {
+ mOnDeleteListener = listener;
+ }
+
@Override
public void onClick(View view) {
- if (view == mDeleteTextView) {
- Editable editable = (Editable) mTextView.getText();
- int start = editable.getSpanStart(mEasyEditSpan);
- int end = editable.getSpanEnd(mEasyEditSpan);
- if (start >= 0 && end >= 0) {
- mTextView.deleteText_internal(start, end);
- }
+ if (view == mDeleteTextView
+ && mEasyEditSpan != null && mEasyEditSpan.isDeleteEnabled()
+ && mOnDeleteListener != null) {
+ mOnDeleteListener.onDeleteClick(mEasyEditSpan);
}
}
@Override
+ public void hide() {
+ if (mEasyEditSpan != null) {
+ mEasyEditSpan.setDeleteEnabled(false);
+ }
+ mOnDeleteListener = null;
+ super.hide();
+ }
+
+ @Override
protected int getTextOffset() {
// Place the pop-up at the end of the span
Editable editable = (Editable) mTextView.getText();
diff --git a/core/java/com/android/internal/statusbar/INotificationListener.java b/core/java/com/android/internal/statusbar/INotificationListener.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/INotificationListener.java
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 7b80abc..988951c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -149,9 +149,17 @@
}
}
public float getXVelocity() {
+ if (Float.isNaN(mVX)) {
+ Slog.v("FlingTracker", "warning: vx=NaN");
+ // XXX: should return 0
+ }
return mVX;
}
public float getYVelocity() {
+ if (Float.isNaN(mVY)) {
+ Slog.v("FlingTracker", "warning: vx=NaN");
+ // XXX: should return 0
+ }
return mVY;
}
public void recycle() {
@@ -284,6 +292,9 @@
|| ((mRubberbanding || !mClosing) && mExpandedHeight == fh)) {
post(mStopAnimator);
}
+ } else {
+ Slog.v(TAG, "animationTick called with dtms=" + dtms + "; nothing to do (h="
+ + mExpandedHeight + " v=" + mVel + ")");
}
}
@@ -374,7 +385,7 @@
case MotionEvent.ACTION_MOVE:
final float h = rawY - mAbsPos[1] - mTouchOffset;
if (h > mPeekHeight) {
- if (mPeekAnimator != null && mPeekAnimator.isRunning()) {
+ if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
mPeekAnimator.cancel();
}
mJustPeeked = false;
@@ -505,7 +516,7 @@
public void setExpandedHeight(float height) {
if (DEBUG) LOG("setExpandedHeight(%.1f)", height);
mRubberbanding = false;
- if (mTimeAnimator.isRunning()) {
+ if (mTimeAnimator.isStarted()) {
post(mStopAnimator);
}
setExpandedHeightInternal(height);
@@ -519,6 +530,11 @@
}
public void setExpandedHeightInternal(float h) {
+ if (Float.isNaN(h)) {
+ Slog.v(TAG, "setExpandedHeightInternal: warning: h=NaN");
+ // XXX: should set h to 0
+ }
+
float fh = getFullHeight();
if (fh == 0) {
// Hmm, full height hasn't been computed yet
@@ -526,6 +542,7 @@
if (h < 0) h = 0;
if (!(mRubberbandingEnabled && (mTracking || mRubberbanding)) && h > fh) h = fh;
+
mExpandedHeight = h;
if (DEBUG) LOG("setExpansion: height=%.1f fh=%.1f tracking=%s rubber=%s", h, fh, mTracking?"T":"f", mRubberbanding?"T":"f");
@@ -548,6 +565,10 @@
}
public void setExpandedFraction(float frac) {
+ if (Float.isNaN(frac)) {
+ Slog.v(TAG, "setExpandedFraction: frac=NaN");
+ // XXX: set frac to 0 to defend
+ }
setExpandedHeight(getFullHeight() * frac);
}
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 13bf39f..9f2685b 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -26,6 +26,7 @@
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.INotificationManager;
+import android.app.INotificationListener;
import android.app.ITransientNotification;
import android.app.Notification;
import android.app.PendingIntent;
@@ -151,6 +152,7 @@
private boolean mInCall = false;
private boolean mNotificationPulseEnabled;
+ // used as a mutex for access to all active notifications & listeners
private final ArrayList<NotificationRecord> mNotificationList =
new ArrayList<NotificationRecord>();
@@ -161,6 +163,8 @@
private final AppOpsManager mAppOps;
+ private ArrayList<NotificationListenerInfo> mListeners = new ArrayList<NotificationListenerInfo>();
+
// Notification control database. For now just contains disabled packages.
private AtomicFile mPolicyFile;
private HashSet<String> mBlockedPackages = new HashSet<String>();
@@ -174,6 +178,38 @@
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME = "name";
+ private class NotificationListenerInfo implements DeathRecipient {
+ INotificationListener listener;
+ int userid;
+ public NotificationListenerInfo(INotificationListener listener, int userid) {
+ this.listener = listener;
+ this.userid = userid;
+ }
+
+ public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
+ if (this.userid != sbn.getUserId()) return;
+ try {
+ listener.onNotificationPosted(sbn);
+ } catch (RemoteException ex) {
+ // not there?
+ }
+ }
+
+ public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
+ if (this.userid != sbn.getUserId()) return;
+ try {
+ listener.onNotificationRemoved(sbn);
+ } catch (RemoteException ex) {
+ // not there?
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ unregisterListener(this.listener, this.userid);
+ }
+ }
+
private static class Archive {
static final int BUFFER_SIZE = 1000;
ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE);
@@ -406,6 +442,56 @@
return tmp;
}
+ @Override
+ public void registerListener(final INotificationListener listener, final int userid) {
+ checkCallerIsSystem();
+ synchronized (mNotificationList) {
+ try {
+ NotificationListenerInfo info = new NotificationListenerInfo(listener, userid);
+ listener.asBinder().linkToDeath(info, 0);
+ mListeners.add(info);
+ } catch (RemoteException e) {
+ // already dead
+ }
+ }
+ }
+
+ @Override
+ public void unregisterListener(INotificationListener listener, int userid) {
+ checkCallerIsSystem();
+ synchronized (mNotificationList) {
+ final int N = mListeners.size();
+ for (int i=N-1; i>=0; i--) {
+ final NotificationListenerInfo info = mListeners.get(i);
+ if (info.listener == listener && info.userid == userid) {
+ mListeners.remove(listener);
+ }
+ }
+ }
+ }
+
+ private void notifyPostedLocked(NotificationRecord n) {
+ final StatusBarNotification sbn = n.sbn;
+ for (final NotificationListenerInfo info : mListeners) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ info.notifyPostedIfUserMatch(sbn);
+ }});
+ }
+ }
+
+ private void notifyRemovedLocked(NotificationRecord n) {
+ final StatusBarNotification sbn = n.sbn;
+ for (final NotificationListenerInfo info : mListeners) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ info.notifyRemovedIfUserMatch(sbn);
+ }});
+ }
+ }
+
public static final class NotificationRecord
{
final StatusBarNotification sbn;
@@ -1165,6 +1251,8 @@
// finally, keep some of this information around for later use
mArchive.record(n);
+
+ notifyPostedLocked(r);
} else {
Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
@@ -1175,6 +1263,8 @@
finally {
Binder.restoreCallingIdentity(identity);
}
+
+ notifyRemovedLocked(r);
}
return; // do not play sounds, show lights, etc. for invalid notifications
}
@@ -1341,6 +1431,7 @@
Binder.restoreCallingIdentity(identity);
}
r.statusBarKey = null;
+ notifyRemovedLocked(r);
}
// sound