Merge "Support for "streaming" non-transport streams by first converting them to ts packets."
diff --git a/api/current.txt b/api/current.txt
index 90890d6..d90dbab 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9397,6 +9397,7 @@
method public void onUpdateExtractingViews(android.view.inputmethod.EditorInfo);
method public void onUpdateExtractingVisibility(android.view.inputmethod.EditorInfo);
method public void onUpdateSelection(int, int, int, int, int, int);
+ method public void onViewClicked(boolean);
method public void onWindowHidden();
method public void onWindowShown();
method public void requestHideSelf(int);
@@ -9440,6 +9441,7 @@
method public void updateCursor(android.graphics.Rect);
method public void updateExtractedText(int, android.view.inputmethod.ExtractedText);
method public void updateSelection(int, int, int, int, int, int);
+ method public void viewClicked(boolean);
}
public static final class InputMethodService.Insets {
@@ -11240,7 +11242,8 @@
method public static long getUidUdpRxPackets(int);
method public static long getUidUdpTxBytes(int);
method public static long getUidUdpTxPackets(int);
- method public static void setThreadStatsTag(java.lang.String);
+ method public static void setThreadStatsTag(int);
+ method public static deprecated void setThreadStatsTag(java.lang.String);
method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
field public static final int UNSUPPORTED = -1; // 0xffffffff
@@ -11446,11 +11449,14 @@
}
public class SslError {
- ctor public SslError(int, android.net.http.SslCertificate);
- ctor public SslError(int, java.security.cert.X509Certificate);
+ ctor public deprecated SslError(int, android.net.http.SslCertificate);
+ ctor public deprecated SslError(int, java.security.cert.X509Certificate);
+ ctor public SslError(int, android.net.http.SslCertificate, java.lang.String);
+ ctor public SslError(int, java.security.cert.X509Certificate, java.lang.String);
method public boolean addError(int);
method public android.net.http.SslCertificate getCertificate();
method public int getPrimaryError();
+ method public java.lang.String getUrl();
method public boolean hasError(int);
field public static final int SSL_EXPIRED = 1; // 0x1
field public static final int SSL_IDMISMATCH = 2; // 0x2
@@ -23507,6 +23513,7 @@
method public void updateCursor(android.view.View, int, int, int, int);
method public void updateExtractedText(android.view.View, int, android.view.inputmethod.ExtractedText);
method public void updateSelection(android.view.View, int, int, int, int);
+ method public void viewClicked(android.view.View);
field public static final int HIDE_IMPLICIT_ONLY = 1; // 0x1
field public static final int HIDE_NOT_ALWAYS = 2; // 0x2
field public static final int RESULT_HIDDEN = 3; // 0x3
@@ -23527,6 +23534,7 @@
method public abstract void updateCursor(android.graphics.Rect);
method public abstract void updateExtractedText(int, android.view.inputmethod.ExtractedText);
method public abstract void updateSelection(int, int, int, int, int, int);
+ method public abstract void viewClicked(boolean);
}
public static abstract interface InputMethodSession.EventCallback {
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index df8cf9a..e10f218 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -47,6 +47,7 @@
private static final int DO_APP_PRIVATE_COMMAND = 100;
private static final int DO_TOGGLE_SOFT_INPUT = 105;
private static final int DO_FINISH_SESSION = 110;
+ private static final int DO_VIEW_CLICKED = 115;
HandlerCaller mCaller;
InputMethodSession mInputMethodSession;
@@ -133,6 +134,10 @@
mInputMethodSession = null;
return;
}
+ case DO_VIEW_CLICKED: {
+ mInputMethodSession.viewClicked(msg.arg1 == 1);
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -167,7 +172,11 @@
oldSelStart, oldSelEnd, newSelStart, newSelEnd,
candidatesStart, candidatesEnd));
}
-
+
+ public void viewClicked(boolean focusChanged) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
+ }
+
public void updateCursor(Rect newCursor) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UPDATE_CURSOR,
newCursor));
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index feb246e..9481a88 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -488,7 +488,15 @@
InputMethodService.this.onUpdateSelection(oldSelStart, oldSelEnd,
newSelStart, newSelEnd, candidatesStart, candidatesEnd);
}
-
+
+ @Override
+ public void viewClicked(boolean focusChanged) {
+ if (!isEnabled()) {
+ return;
+ }
+ InputMethodService.this.onViewClicked(focusChanged);
+ }
+
/**
* Call {@link InputMethodService#onUpdateCursor
* InputMethodService.onUpdateCursor()}.
@@ -1609,6 +1617,16 @@
}
/**
+ * Called when the user tapped or clicked a text view.
+ * IMEs can't rely on this method being called because this was not part of the original IME
+ * protocol, so applications with custom text editing written before this method appeared will
+ * not call to inform the IME of this interaction.
+ * @param focusChanged true if the user changed the focused view by this click.
+ */
+ public void onViewClicked(boolean focusChanged) {
+ }
+
+ /**
* Called when the application has reported a new location of its text
* cursor. This is only called if explicitly requested by the input method.
* The default implementation does nothing.
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 91af16d..21fad2c 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -16,14 +16,21 @@
package android.net;
+import static android.content.pm.PackageManager.GET_SIGNATURES;
import static android.text.format.Time.MONTH_DAY;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
import android.os.RemoteException;
import android.text.format.Time;
+import com.google.android.collect.Sets;
+
import java.io.PrintWriter;
+import java.util.HashSet;
/**
* Manager for creating and modifying network policy rules.
@@ -210,8 +217,35 @@
* usually to protect critical system services.
*/
public static boolean isUidValidForPolicy(Context context, int uid) {
- return (uid >= android.os.Process.FIRST_APPLICATION_UID
- && uid <= android.os.Process.LAST_APPLICATION_UID);
+ // first, quick-reject non-applications
+ if (uid < android.os.Process.FIRST_APPLICATION_UID
+ || uid > android.os.Process.LAST_APPLICATION_UID) {
+ return false;
+ }
+
+ final PackageManager pm = context.getPackageManager();
+ final HashSet<Signature> systemSignature;
+ try {
+ systemSignature = Sets.newHashSet(
+ pm.getPackageInfo("android", GET_SIGNATURES).signatures);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException("problem finding system signature", e);
+ }
+
+ try {
+ // reject apps signed with system cert
+ for (String packageName : pm.getPackagesForUid(uid)) {
+ final HashSet<Signature> packageSignature = Sets.newHashSet(
+ pm.getPackageInfo(packageName, GET_SIGNATURES).signatures);
+ if (packageSignature.containsAll(systemSignature)) {
+ return false;
+ }
+ }
+ } catch (NameNotFoundException e) {
+ }
+
+ // nothing found above; we can apply policy to UID
+ return true;
}
/** {@hide} */
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index cb47193..2b59dba 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -16,14 +16,18 @@
package android.net;
+import android.app.DownloadManager;
+import android.app.backup.BackupManager;
import android.content.Context;
+import android.media.MediaPlayer;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.ServiceManager;
-import dalvik.system.BlockGuard;
+import com.android.server.NetworkManagementSocketTagger;
+import dalvik.system.SocketTagger;
import java.net.Socket;
import java.net.SocketException;
@@ -50,6 +54,27 @@
public static final int UID_REMOVED = -4;
/**
+ * Default tag value for {@link DownloadManager} traffic.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_DOWNLOAD = 0xFFFF0001;
+
+ /**
+ * Default tag value for {@link MediaPlayer} traffic.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_MEDIA = 0xFFFF0002;
+
+ /**
+ * Default tag value for {@link BackupManager} traffic.
+ *
+ * @hide
+ */
+ public static final int TAG_SYSTEM_BACKUP = 0xFFFF0003;
+
+ /**
* Snapshot of {@link NetworkStats} when the currently active profiling
* session started, or {@code null} if no session active.
*
@@ -67,12 +92,20 @@
* Changes only take effect during subsequent calls to
* {@link #tagSocket(Socket)}.
*/
+ public static void setThreadStatsTag(int tag) {
+ NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
+ }
+
+ /**
+ * @deprecated unsupported, will eventually be removed
+ */
+ @Deprecated
public static void setThreadStatsTag(String tag) {
- BlockGuard.setThreadSocketStatsTag(tag);
+ setThreadStatsTag(tag.hashCode());
}
public static void clearThreadStatsTag() {
- BlockGuard.setThreadSocketStatsTag(null);
+ NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
}
/**
@@ -89,12 +122,12 @@
* {@hide}
*/
public static void setThreadStatsUid(int uid) {
- BlockGuard.setThreadSocketStatsUid(uid);
+ NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
}
/** {@hide} */
public static void clearThreadStatsUid() {
- BlockGuard.setThreadSocketStatsUid(-1);
+ NetworkManagementSocketTagger.setThreadSocketStatsUid(-1);
}
/**
@@ -103,18 +136,18 @@
* parameters. When finished, call {@link #untagSocket(Socket)} to remove
* statistics parameters.
*
- * @see #setThreadStatsTag(String)
+ * @see #setThreadStatsTag(int)
* @see #setThreadStatsUid(int)
*/
public static void tagSocket(Socket socket) throws SocketException {
- BlockGuard.tagSocketFd(socket.getFileDescriptor$());
+ SocketTagger.get().tag(socket);
}
/**
* Remove any statistics parameters from the given {@link Socket}.
*/
public static void untagSocket(Socket socket) throws SocketException {
- BlockGuard.untagSocketFd(socket.getFileDescriptor$());
+ SocketTagger.get().untag(socket);
}
/**
diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java
index e1b9deb..1e1cb49 100644
--- a/core/java/android/net/http/SslError.java
+++ b/core/java/android/net/http/SslError.java
@@ -59,36 +59,97 @@
/**
* The SSL certificate associated with the error set
*/
- SslCertificate mCertificate;
+ final SslCertificate mCertificate;
+
+ /**
+ * The URL associated with the error set.
+ */
+ final String mUrl;
/**
* Creates a new SSL error set object
* @param error The SSL error
* @param certificate The associated SSL certificate
+ * @deprecated Use {@link #SslError(int, SslCertificate, String)}
*/
+ @Deprecated
public SslError(int error, SslCertificate certificate) {
addError(error);
+ if (certificate == null) {
+ throw new NullPointerException("certificate is null.");
+ }
mCertificate = certificate;
+ mUrl = "";
}
/**
* Creates a new SSL error set object
* @param error The SSL error
* @param certificate The associated SSL certificate
+ * @deprecated Use {@link #SslError(int, X509Certificate, String)}
*/
+ @Deprecated
public SslError(int error, X509Certificate certificate) {
addError(error);
+ if (certificate == null) {
+ throw new NullPointerException("certificate is null.");
+ }
mCertificate = new SslCertificate(certificate);
+ mUrl = "";
}
/**
- * @return The SSL certificate associated with the error set
+ * Creates a new SSL error set object
+ * @param error The SSL error
+ * @param certificate The associated SSL certificate
+ * @param url The associated URL.
+ */
+ public SslError(int error, SslCertificate certificate, String url) {
+ addError(error);
+ if (certificate == null) {
+ throw new NullPointerException("certificate is null.");
+ }
+ mCertificate = certificate;
+ if (url == null) {
+ throw new NullPointerException("url is null.");
+ }
+ mUrl = url;
+ }
+
+ /**
+ * Creates a new SSL error set object
+ * @param error The SSL error
+ * @param certificate The associated SSL certificate
+ * @param url The associated URL.
+ */
+ public SslError(int error, X509Certificate certificate, String url) {
+ addError(error);
+ if (certificate == null) {
+ throw new NullPointerException("certificate is null.");
+ }
+ mCertificate = new SslCertificate(certificate);
+ if (url == null) {
+ throw new NullPointerException("url is null.");
+ }
+ mUrl = url;
+ }
+
+ /**
+ * @return The SSL certificate associated with the error set, non-null.
*/
public SslCertificate getCertificate() {
return mCertificate;
}
/**
+ * @return The URL associated with the error set, non-null.
+ * "" if one of the deprecated constructors is used.
+ */
+ public String getUrl() {
+ return mUrl;
+ }
+
+ /**
* Adds the SSL error to the error set
* @param error The SSL error to add
* @return True iff the error being added is a known SSL error
@@ -137,6 +198,7 @@
*/
public String toString() {
return "primary error: " + getPrimaryError() +
- " certificate: " + getCertificate();
+ " certificate: " + getCertificate() +
+ " on URL: " + getUrl();
}
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 0933193..39c6f57 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -57,6 +57,29 @@
Uri.parse("content://call_log/calls/filter");
/**
+ * An optional URI parameter which instructs the provider to allow the operation to be
+ * applied to voicemail records as well.
+ * <p>
+ * TYPE: Boolean
+ * <p>
+ * Using this parameter with a value of {@code true} will result in a security error if the
+ * calling package does not have appropriate permissions to access voicemails.
+ *
+ * @hide
+ */
+ public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails";
+
+ /**
+ * Content uri with {@link #ALLOW_VOICEMAILS_PARAM_KEY} set. This can directly be used to
+ * access call log entries that includes voicemail records.
+ *
+ * @hide
+ */
+ public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon()
+ .appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true")
+ .build();
+
+ /**
* The default sort order for this table
*/
public static final String DEFAULT_SORT_ORDER = "date DESC";
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 49f3bbe..9b081b2 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -239,7 +239,7 @@
break;
}
} finally {
- finishEvent(false);
+ finishEvent();
}
}
@@ -302,7 +302,7 @@
problem("Source was not SOURCE_CLASS_TRACKBALL.");
}
} finally {
- finishEvent(false);
+ finishEvent();
}
}
@@ -328,7 +328,9 @@
mTouchEventStreamUnhandled = false;
mTouchEventStreamPointers = 0;
}
- final boolean wasTainted = mTouchEventStreamIsTainted;
+ if (mTouchEventStreamIsTainted) {
+ event.setTainted(true);
+ }
try {
ensureMetaStateIsNormalized(event.getMetaState());
@@ -441,7 +443,7 @@
problem("Source was not SOURCE_CLASS_POINTER.");
}
} finally {
- finishEvent(wasTainted);
+ finishEvent();
}
}
@@ -499,7 +501,7 @@
}
}
} finally {
- finishEvent(false);
+ finishEvent();
}
}
@@ -591,9 +593,9 @@
return true;
}
- private void finishEvent(boolean tainted) {
+ private void finishEvent() {
if (mViolationMessage != null && mViolationMessage.length() != 0) {
- if (!tainted) {
+ if (!mCurrentEvent.isTainted()) {
// Write a log message only if the event was not already tainted.
mViolationMessage.append("\n in ").append(mCaller);
mViolationMessage.append("\n ");
@@ -614,17 +616,14 @@
}
Log.d(mLogTag, mViolationMessage.toString());
- tainted = true;
+
+ // Taint the event so that we do not generate additional violations from it
+ // further downstream.
+ mCurrentEvent.setTainted(true);
}
mViolationMessage.setLength(0);
}
- if (tainted) {
- // Taint the event so that we do not generate additional violations from it
- // further downstream.
- mCurrentEvent.setTainted(true);
- }
-
if (RECENT_EVENTS_TO_LOG != 0) {
if (mRecentEvents == null) {
mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index f45e78b..88f59d4 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2885,7 +2885,7 @@
toolTypeToString(getToolType(i)));
}
- msg.append(", buttonState=").append(KeyEvent.metaStateToString(getButtonState()));
+ msg.append(", buttonState=").append(MotionEvent.buttonStateToString(getButtonState()));
msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState()));
msg.append(", flags=0x").append(Integer.toHexString(getFlags()));
msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags()));
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 47f5e4c..a1a7281 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1126,7 +1126,7 @@
if (mServedView == mNextServedView && !mNextServedNeedsStart) {
return;
}
-
+
InputConnection ic = null;
synchronized (mH) {
if (mServedView == mNextServedView && !mNextServedNeedsStart) {
@@ -1242,6 +1242,27 @@
}
/**
+ * Notify the event when the user tapped or clicked the text view.
+ */
+ public void viewClicked(View view) {
+ final boolean focusChanged = mServedView != mNextServedView;
+ checkFocus();
+ synchronized (mH) {
+ if ((mServedView != view && (mServedView == null
+ || !mServedView.checkInputConnectionProxy(view)))
+ || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ return;
+ }
+ try {
+ if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
+ mCurMethod.viewClicked(focusChanged);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
+ }
+ }
+ }
+
+ /**
* Returns true if the current input method wants to watch the location
* of the input editor's cursor in its window.
*/
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index bb03afa..ea6f5ee 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -63,6 +63,15 @@
int candidatesStart, int candidatesEnd);
/**
+ * This method is called when the user tapped a text view.
+ * IMEs can't rely on this method being called because this was not part of the original IME
+ * protocol, so applications with custom text editing written before this method appeared will
+ * not call to inform the IME of this interaction.
+ * @param focusChanged true if the user changed the focused view by this click.
+ */
+ public void viewClicked(boolean focusChanged);
+
+ /**
* This method is called when cursor location of the target input field
* has changed within its window. This is not normally called, but will
* only be reported if requested by the input method.
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 2f4774f..79a5aff 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -1150,11 +1150,12 @@
* {@link #nativeSslCertErrorProceed(int)} or
* {@link #nativeSslCertErrorCancel(int, int)}.
*/
- private void reportSslCertError(final int handle, final int cert_error, byte cert_der[]) {
+ private void reportSslCertError(
+ final int handle, final int cert_error, byte cert_der[], String url) {
final SslError ssl_error;
try {
X509Certificate cert = new X509CertImpl(cert_der);
- ssl_error = new SslError(cert_error, cert);
+ ssl_error = new SslError(cert_error, cert, url);
} catch (IOException e) {
// Can't get the certificate, not much to do.
Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 2947ebe..712ecca 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -549,11 +549,13 @@
final ValueAnimator fadeScroller = ObjectAnimator.ofInt(this, "selectorPaintAlpha", 255, 0);
final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton,
"alpha", 0, 1);
+ final ObjectAnimator showInputText = ObjectAnimator.ofFloat(mInputText,
+ "alpha", 0, 1);
final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton,
"alpha", 0, 1);
mShowInputControlsAnimator = new AnimatorSet();
mShowInputControlsAnimator.playTogether(fadeScroller, showIncrementButton,
- showDecrementButton);
+ showInputText, showDecrementButton);
mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mCanceled = false;
@@ -1205,13 +1207,11 @@
}
int[] selectorIndices = getSelectorIndices();
int totalTextHeight = selectorIndices.length * mTextSize;
- int totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
- int textGapCount = selectorIndices.length - 1;
- int selectorTextGapHeight = totalTextGapHeight / textGapCount;
- // compensate for integer division loss of the components used to
- // calculate the text gap
- int integerDivisionLoss = (mTextSize + mBottom - mTop) % textGapCount;
- mInitialScrollOffset = mCurrentScrollOffset = mTextSize - integerDivisionLoss / 2;
+ float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
+ float textGapCount = selectorIndices.length - 1;
+ int selectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
+ // Compensate if text size is odd since every time we get its middle a pixel is lost.
+ mInitialScrollOffset = mCurrentScrollOffset = mTextSize - (3 * (mTextSize % 2));
mSelectorElementHeight = mTextSize + selectorTextGapHeight;
updateInputTextView();
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 85e7eec..2f9bd69 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4948,7 +4948,10 @@
if (mMovement != null && mText instanceof Editable
&& mLayout != null && onCheckIsTextEditor()) {
InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) imm.showSoftInput(this, 0);
+ if (imm != null) {
+ imm.viewClicked(this);
+ imm.showSoftInput(this, 0);
+ }
}
}
}
@@ -7398,8 +7401,11 @@
if ((isTextEditable() || mTextIsSelectable) && touchIsFinished) {
// Show the IME, except when selecting in read-only text.
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.viewClicked(this);
+ }
if (!mTextIsSelectable) {
- final InputMethodManager imm = InputMethodManager.peekInstance();
handled |= imm != null && imm.showSoftInput(this, 0);
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 5e9cd23..f13e770 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -25,16 +25,13 @@
import android.os.SystemProperties;
import android.util.Log;
import android.util.Slog;
-
import com.android.internal.logging.AndroidConfig;
-
+import com.android.server.NetworkManagementSocketTagger;
import dalvik.system.VMRuntime;
-
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.util.logging.LogManager;
import java.util.TimeZone;
-
+import java.util.logging.LogManager;
import org.apache.harmony.luni.internal.util.TimezoneGetter;
/**
@@ -129,6 +126,11 @@
System.setProperty("http.agent", userAgent);
/*
+ * Wire socket tagging to traffic stats.
+ */
+ NetworkManagementSocketTagger.install();
+
+ /*
* If we're running in an emulator launched with "-trace", put the
* VM into emulator trace profiling mode so that the user can hit
* F9/F10 at any time to capture traces. This has performance
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index 338dcaa..f875cbd 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -36,7 +36,9 @@
void updateSelection(int oldSelStart, int oldSelEnd,
int newSelStart, int newSelEnd,
int candidatesStart, int candidatesEnd);
-
+
+ void viewClicked(boolean focusChanged);
+
void updateCursor(in Rect newCursor);
void displayCompletions(in CompletionInfo[] completions);
diff --git a/core/java/com/android/server/NetworkManagementSocketTagger.java b/core/java/com/android/server/NetworkManagementSocketTagger.java
new file mode 100644
index 0000000..306d223
--- /dev/null
+++ b/core/java/com/android/server/NetworkManagementSocketTagger.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2011 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 com.android.server;
+
+import dalvik.system.SocketTagger;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.SocketException;
+import java.nio.charset.Charsets;
+
+/**
+ * Assigns tags to sockets for traffic stats.
+ */
+public final class NetworkManagementSocketTagger extends SocketTagger {
+
+ private static final boolean LOGI = false;
+
+ private static ThreadLocal<SocketTags> threadSocketTags = new ThreadLocal<SocketTags>() {
+ @Override protected SocketTags initialValue() {
+ return new SocketTags();
+ }
+ };
+
+ public static void install() {
+ SocketTagger.set(new NetworkManagementSocketTagger());
+ }
+
+ public static void setThreadSocketStatsTag(int tag) {
+ threadSocketTags.get().statsTag = tag;
+ }
+
+ public static void setThreadSocketStatsUid(int uid) {
+ threadSocketTags.get().statsUid = uid;
+ }
+
+ @Override public void tag(FileDescriptor fd) throws SocketException {
+ final SocketTags options = threadSocketTags.get();
+ if (LOGI) {
+ System.logI("tagSocket(" + fd.getInt$() + ") with statsTag="
+ + options.statsTag + ", statsUid=" + options.statsUid);
+ }
+ try {
+ // TODO: skip tagging when options would be no-op
+ tagSocketFd(fd, options.statsTag, options.statsUid);
+ } catch (IOException e) {
+ throw new SocketException("Problem tagging socket", e);
+ }
+ }
+
+ private void tagSocketFd(FileDescriptor fd, int tag, int uid) throws IOException {
+ final int fdNum = fd.getInt$();
+ if (fdNum == -1 || (tag == -1 && uid == -1)) return;
+
+ String cmd = "t " + fdNum;
+ if (tag == -1) {
+ // Case where just the uid needs adjusting. But probably the caller
+ // will want to track his own name here, just in case.
+ cmd += " 0";
+ } else {
+ cmd += " " + tagToKernel(tag);
+ }
+ if (uid != -1) {
+ cmd += " " + uid;
+ }
+ internalModuleCtrl(cmd);
+ }
+
+ @Override public void untag(FileDescriptor fd) throws SocketException {
+ if (LOGI) {
+ System.logI("untagSocket(" + fd.getInt$() + ")");
+ }
+ try {
+ unTagSocketFd(fd);
+ } catch (IOException e) {
+ throw new SocketException("Problem untagging socket", e);
+ }
+ }
+
+ private void unTagSocketFd(FileDescriptor fd) throws IOException {
+ int fdNum = fd.getInt$();
+ if (fdNum == -1) return;
+ String cmd = "u " + fdNum;
+ internalModuleCtrl(cmd);
+ }
+
+ public static class SocketTags {
+ public int statsTag = -1;
+ public int statsUid = -1;
+ }
+
+ /**
+ * Sends commands to the kernel netfilter module.
+ *
+ * @param cmd command string for the qtaguid netfilter module. May not be null.
+ * <p>Supports:
+ * <ul><li>tag a socket:<br>
+ * <code>t <i>sock_fd</i> <i>acct_tag</i> [<i>uid_in_case_caller_is_acting_on_behalf_of</i>]</code><br>
+ * <code>*_tag</code> defaults to default_policy_tag_from_uid(uid_of_caller)<br>
+ * <code>acct_tag</code> is either 0 or greater that 2^32.<br>
+ * <code>uid_*</code> is only settable by privileged UIDs (DownloadManager,...)
+ * </li>
+ * <li>untag a socket, preserving counters:<br>
+ * <code>u <i>sock_fd</i></code>
+ * </li></ul>
+ * <p>Notes:<br>
+ * <ul><li><i>sock_fd</i> is withing the callers process space.</li>
+ * <li><i>*_tag</i> are 64bit values</li></ul>
+ *
+ */
+ private void internalModuleCtrl(String cmd) throws IOException {
+ final FileOutputStream procOut;
+ // TODO: Use something like
+ // android.os.SystemProperties.getInt("persist.bandwidth.enable", 0)
+ // to see if tagging should happen or not.
+ try {
+ procOut = new FileOutputStream("/proc/net/xt_qtaguid/ctrl");
+ } catch (FileNotFoundException e) {
+ if (LOGI) {
+ System.logI("Can't talk to kernel module:" + e);
+ }
+ return;
+ }
+ try {
+ procOut.write(cmd.getBytes(Charsets.US_ASCII));
+ } finally {
+ procOut.close();
+ }
+ }
+
+ /**
+ * Convert {@link Integer} tag to {@code /proc/} format. Assumes unsigned
+ * base-10 format like {@code 2147483647}. Currently strips signed bit to
+ * avoid using {@link java.math.BigInteger}.
+ */
+ public static String tagToKernel(int tag) {
+ // TODO: eventually write in hex, since that's what proc exports
+ // TODO: migrate to direct integer instead of odd shifting
+ return Long.toString((((long) tag) << 32) & 0x7FFFFFFF00000000L);
+ }
+
+ /**
+ * Convert {@code /proc/} tag format to {@link Integer}. Assumes incoming
+ * format like {@code 0x7fffffff00000000}.
+ */
+ public static int kernelToTag(String string) {
+ // TODO: migrate to direct integer instead of odd shifting
+ return (int) (Long.decode(string) >> 32);
+ }
+}
diff --git a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png b/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png
deleted file mode 100644
index 1f7aeee..0000000
--- a/core/res/res/drawable-hdpi/btn_check_on_disable_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png b/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png
deleted file mode 100755
index 4c1d89d..0000000
--- a/core/res/res/drawable-hdpi/btn_toggle_on_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_toggle_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/btn_toggle_pressed_holo_light.9.png
deleted file mode 100644
index 6963a0e..0000000
--- a/core/res/res/drawable-hdpi/btn_toggle_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/cab_holo_dark.9.png b/core/res/res/drawable-hdpi/cab_holo_dark.9.png
deleted file mode 100755
index 64e2052..0000000
--- a/core/res/res/drawable-hdpi/cab_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png b/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png
deleted file mode 100644
index 3a84de9..0000000
--- a/core/res/res/drawable-hdpi/dialog_bottom_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png b/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png
deleted file mode 100644
index 48cc017..0000000
--- a/core/res/res/drawable-mdpi/btn_check_on_disable_focused_holo_dark.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png b/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png
deleted file mode 100755
index 66cbe48..0000000
--- a/core/res/res/drawable-mdpi/btn_toggle_on_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_toggle_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/btn_toggle_pressed_holo_light.9.png
deleted file mode 100644
index b6508fc..0000000
--- a/core/res/res/drawable-mdpi/btn_toggle_pressed_holo_light.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/cab_holo_dark.9.png b/core/res/res/drawable-mdpi/cab_holo_dark.9.png
deleted file mode 100755
index 7daae1f..0000000
--- a/core/res/res/drawable-mdpi/cab_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png b/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png
deleted file mode 100644
index cb3d0f2..0000000
--- a/core/res/res/drawable-mdpi/dialog_bottom_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/layout-sw600dp/date_picker_dialog.xml b/core/res/res/layout-sw600dp/date_picker_dialog.xml
new file mode 100644
index 0000000..004d52a
--- /dev/null
+++ b/core/res/res/layout-sw600dp/date_picker_dialog.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2007, 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.
+*/
+-->
+
+<DatePicker xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/datePicker"
+ android:layout_gravity="center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
diff --git a/core/res/res/layout-sw600dp/number_picker.xml b/core/res/res/layout-sw600dp/number_picker.xml
new file mode 100644
index 0000000..807daf2
--- /dev/null
+++ b/core/res/res/layout-sw600dp/number_picker.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2008, 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.
+*/
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <ImageButton android:id="@+id/increment"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/numberPickerUpButtonStyle"
+ android:contentDescription="@string/number_picker_increment_button" />
+
+ <EditText android:id="@+id/numberpicker_input"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/numberPickerInputTextStyle" />
+
+ <ImageButton android:id="@+id/decrement"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/numberPickerDownButtonStyle"
+ android:contentDescription="@string/number_picker_decrement_button" />
+
+</merge>
diff --git a/core/res/res/layout/date_picker_dialog.xml b/core/res/res/layout/date_picker_dialog.xml
index 004d52a..db8f311 100644
--- a/core/res/res/layout/date_picker_dialog.xml
+++ b/core/res/res/layout/date_picker_dialog.xml
@@ -22,4 +22,6 @@
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:spinnersShown="true"
+ android:calendarViewShown="false"
/>
diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml
index 807daf2..f2f524c 100644
--- a/core/res/res/layout/number_picker.xml
+++ b/core/res/res/layout/number_picker.xml
@@ -23,6 +23,8 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="?android:attr/numberPickerUpButtonStyle"
+ android:paddingTop="22dip"
+ android:paddingBottom="22dip"
android:contentDescription="@string/number_picker_increment_button" />
<EditText android:id="@+id/numberpicker_input"
@@ -34,6 +36,8 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="?android:attr/numberPickerDownButtonStyle"
+ android:paddingTop="22dip"
+ android:paddingBottom="22dip"
android:contentDescription="@string/number_picker_decrement_button" />
</merge>
diff --git a/docs/html/sdk/android-3.1-highlights.jd b/docs/html/sdk/android-3.1-highlights.jd
index 3d132a3..88bc1ee 100644
--- a/docs/html/sdk/android-3.1-highlights.jd
+++ b/docs/html/sdk/android-3.1-highlights.jd
@@ -143,8 +143,8 @@
<p>To make the platform even better for gaming, Android 3.1 adds support for
most PC joysticks and gamepads that are connected over USB or Bluetooth HID.</p>
-<p>For example, users can connect Sony Playstation™ 3 and XBox 360™ game
-controllers over USB (but not Bluetooth), Logitech Dual Action™ gamepads and
+<p>For example, users can connect PlayStation<sup>®</sup>3 and Xbox 360<sup>®</sup>
+game controllers over USB (but not Bluetooth), Logitech Dual Action™ gamepads and
flight sticks, or a car racing controller. Game controllers that use proprietary
networking or pairing are not supported by default, but in general, the platform
supports most PC-connectible joysticks and gamepads.</p>
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index ffdfe66..c46d6f4 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -443,7 +443,8 @@
if (! mPinned || ! mMotionEventSampleDataTail) {
LOGE("channel '%s' publisher ~ Cannot append motion sample because there is no current "
- "AMOTION_EVENT_ACTION_MOVE event.", mChannel->getName().string());
+ "AMOTION_EVENT_ACTION_MOVE or AMOTION_EVENT_ACTION_HOVER_MOVE event.",
+ mChannel->getName().string());
return INVALID_OPERATION;
}
diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java
index 3c05403..939bd4a 100755
--- a/location/java/android/location/Country.java
+++ b/location/java/android/location/Country.java
@@ -19,6 +19,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Locale;
+
/**
* This class wraps the country information.
*
@@ -74,7 +76,7 @@
|| source > COUNTRY_SOURCE_LOCALE) {
throw new IllegalArgumentException();
}
- mCountryIso = countryIso.toLowerCase();
+ mCountryIso = countryIso.toUpperCase(Locale.US);
mSource = source;
}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 6f42596..e3cbd57 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -720,8 +720,9 @@
*
* <p>Since API level 13, if applications set a camera via
* {@link #setCamera(Camera)}, the apps can use the camera after this method
- * call. The apps do not need to lock the camera again. The apps should not
- * start another recording session during recording.
+ * call. The apps do not need to lock the camera again. However, if this
+ * method fails, the apps should still lock the camera back. The apps should
+ * not start another recording session during recording.
*
* @throws IllegalStateException if it is called before
* prepare().
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index bdffce9..ed8149a 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -158,9 +158,12 @@
mVideoSize.width = -1;
mVideoSize.height = -1;
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
mInitCheck = init(camera, proxy, cameraId,
videoSize, frameRate,
storeMetaDataInVideoBuffers);
+ if (mInitCheck != OK) releaseCamera();
+ IPCThreadState::self()->restoreCallingIdentity(token);
}
status_t CameraSource::initCheck() const {
@@ -295,7 +298,6 @@
if (width != -1 && height != -1) {
if (!isVideoSizeSupported(width, height, sizes)) {
LOGE("Video dimension (%dx%d) is unsupported", width, height);
- releaseCamera();
return BAD_VALUE;
}
if (isSetVideoSizeSupportedByCamera) {
@@ -309,7 +311,6 @@
// If one and only one of the width and height is -1
// we reject such a request.
LOGE("Requested video size (%dx%d) is not supported", width, height);
- releaseCamera();
return BAD_VALUE;
} else { // width == -1 && height == -1
// Do not configure the camera.
@@ -327,7 +328,6 @@
if (strstr(supportedFrameRates, buf) == NULL) {
LOGE("Requested frame rate (%d) is not supported: %s",
frameRate, supportedFrameRates);
- releaseCamera();
return BAD_VALUE;
}
@@ -463,7 +463,6 @@
bool storeMetaDataInVideoBuffers) {
status_t err = OK;
- int64_t token = IPCThreadState::self()->clearCallingIdentity();
if ((err = isCameraAvailable(camera, proxy, cameraId)) != OK) {
LOGE("Camera connection could not be established.");
@@ -505,8 +504,6 @@
}
}
- IPCThreadState::self()->restoreCallingIdentity(token);
-
int64_t glitchDurationUs = (1000000LL / mVideoFrameRate);
if (glitchDurationUs > mGlitchDurationThresholdUs) {
mGlitchDurationThresholdUs = glitchDurationUs;
@@ -573,13 +570,21 @@
void CameraSource::releaseCamera() {
LOGV("releaseCamera");
- if ((mCameraFlags & FLAGS_HOT_CAMERA) == 0) {
- LOGV("Camera was cold when we started, stopping preview");
- mCamera->stopPreview();
+ if (mCamera != 0) {
+ if ((mCameraFlags & FLAGS_HOT_CAMERA) == 0) {
+ LOGV("Camera was cold when we started, stopping preview");
+ mCamera->stopPreview();
+ mCamera->disconnect();
+ } else {
+ // Unlock the camera so the application can lock it back.
+ mCamera->unlock();
+ }
+ mCamera.clear();
}
- mCamera.clear();
- mCameraRecordingProxy->asBinder()->unlinkToDeath(mDeathNotifier);
- mCameraRecordingProxy.clear();
+ if (mCameraRecordingProxy != 0) {
+ mCameraRecordingProxy->asBinder()->unlinkToDeath(mDeathNotifier);
+ mCameraRecordingProxy.clear();
+ }
mCameraFlags = 0;
}
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcExecutionEnvironment.java b/nfc-extras/java/com/android/nfc_extras/NfcExecutionEnvironment.java
index eb2f6f8..63c2de2 100644
--- a/nfc-extras/java/com/android/nfc_extras/NfcExecutionEnvironment.java
+++ b/nfc-extras/java/com/android/nfc_extras/NfcExecutionEnvironment.java
@@ -55,6 +55,64 @@
*/
public static final String EXTRA_AID = "com.android.nfc_extras.extra.AID";
+ /**
+ * Broadcast action: A filtered APDU was received.
+ *
+ * <p>This happens when an APDU of interest was matched by the Nfc adapter,
+ * for instance as the result of matching an externally-configured filter.
+ *
+ * <p>The filter configuration mechanism is not currently defined.
+ *
+ * <p>Always contains the extra field {@link EXTRA_APDU_BYTES}.
+ *
+ * @hide
+ */
+ public static final String ACTION_APDU_RECEIVED =
+ "com.android.nfc_extras.action.APDU_RECEIVED";
+
+ /**
+ * Mandatory byte array extra field in {@link #ACTION_APDU_RECEIVED}.
+ *
+ * <p>Contains the bytes of the received APDU.
+ *
+ * @hide
+ */
+ public static final String EXTRA_APDU_BYTES =
+ "com.android.nfc_extras.extra.APDU_BYTES";
+
+ /**
+ * Broadcast action: An EMV card removal event was detected.
+ *
+ * @hide
+ */
+ public static final String ACTION_EMV_CARD_REMOVAL =
+ "com.android.nfc_extras.action.EMV_CARD_REMOVAL";
+
+ /**
+ * Broadcast action: An adapter implementing MIFARE Classic via card
+ * emulation detected that a block has been accessed.
+ *
+ * <p>This may only be issued for the first block that the reader
+ * authenticates to.
+ *
+ * <p>May contain the extra field {@link #EXTRA_MIFARE_BLOCK}.
+ *
+ * @hide
+ */
+ public static final String ACTION_MIFARE_ACCESS_DETECTED =
+ "com.android.nfc_extras.action.MIFARE_ACCESS_DETECTED";
+
+ /**
+ * Optional integer extra field in {@link #ACTION_MIFARE_ACCESS_DETECTED}.
+ *
+ * <p>Provides the block number being accessed. If not set, the block
+ * number being accessed is unknown.
+ *
+ * @hide
+ */
+ public static final String EXTRA_MIFARE_BLOCK =
+ "com.android.nfc_extras.extra.MIFARE_BLOCK";
+
NfcExecutionEnvironment(NfcAdapterExtras extras) {
mExtras = extras;
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index a9aa31b..9469601 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -531,7 +531,12 @@
private void restoreFileData(String filename, BackupDataInput data) {
byte[] bytes = new byte[data.getDataSize()];
if (bytes.length <= 0) return;
- restoreFileData(filename, bytes, bytes.length);
+ try {
+ data.readEntityData(bytes, 0, data.getDataSize());
+ restoreFileData(filename, bytes, bytes.length);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to read file data for " + filename);
+ }
}
private void restoreFileData(String filename, byte[] bytes, int size) {
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 4a50d8a..b9029a7 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -569,9 +569,9 @@
break;
case DROP_REASON_BLOCKED:
LOGI("Dropped event because the current application is not responding and the user "
- "has started interating with a different application.");
+ "has started interacting with a different application.");
reason = "inbound event was dropped because the current application is not responding "
- "and the user has started interating with a different application";
+ "and the user has started interacting with a different application";
break;
case DROP_REASON_STALE:
LOGI("Dropped event because it is stale.");
@@ -1239,37 +1239,35 @@
const InputWindow* newHoverWindow = NULL;
bool isSplit = mTouchState.split;
- bool wrongDevice = mTouchState.down
- && (mTouchState.deviceId != entry->deviceId
- || mTouchState.source != entry->source);
+ bool switchedDevice = mTouchState.deviceId != entry->deviceId
+ || mTouchState.source != entry->source;
bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
|| maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN
|| maskedAction == AMOTION_EVENT_ACTION_SCROLL
|| isHoverAction);
+ bool wrongDevice = false;
if (newGesture) {
bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
- if (wrongDevice && !down) {
+ if (switchedDevice && mTouchState.down && !down) {
+#if DEBUG_FOCUS
+ LOGD("Dropping event because a pointer for a different device is already down.");
+#endif
mTempTouchState.copyFrom(mTouchState);
- } else {
- mTempTouchState.reset();
- mTempTouchState.down = down;
- mTempTouchState.deviceId = entry->deviceId;
- mTempTouchState.source = entry->source;
- isSplit = false;
- wrongDevice = false;
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ switchedDevice = false;
+ wrongDevice = true;
+ goto Failed;
}
+ mTempTouchState.reset();
+ mTempTouchState.down = down;
+ mTempTouchState.deviceId = entry->deviceId;
+ mTempTouchState.source = entry->source;
+ isSplit = false;
} else {
mTempTouchState.copyFrom(mTouchState);
}
- if (wrongDevice) {
-#if DEBUG_FOCUS
- LOGD("Dropping event because a pointer for a different device is already down.");
-#endif
- injectionResult = INPUT_EVENT_INJECTION_FAILED;
- goto Failed;
- }
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
/* Case 1: New splittable pointer going down, or need target for hover or scroll. */
@@ -1598,18 +1596,38 @@
// Update final pieces of touch state if the injector had permission.
if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
if (!wrongDevice) {
- if (maskedAction == AMOTION_EVENT_ACTION_UP
- || maskedAction == AMOTION_EVENT_ACTION_CANCEL
- || isHoverAction) {
+ if (switchedDevice) {
+#if DEBUG_FOCUS
+ LOGD("Conflicting pointer actions: Switched to a different device.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+
+ if (isHoverAction) {
+ // Started hovering, therefore no longer down.
+ if (mTouchState.down) {
+#if DEBUG_FOCUS
+ LOGD("Conflicting pointer actions: Hover received while pointer was down.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+ mTouchState.reset();
+ if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ mTouchState.deviceId = entry->deviceId;
+ mTouchState.source = entry->source;
+ }
+ } else if (maskedAction == AMOTION_EVENT_ACTION_UP
+ || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
// All pointers up or canceled.
mTouchState.reset();
} else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
// First pointer went down.
if (mTouchState.down) {
- *outConflictingPointerActions = true;
#if DEBUG_FOCUS
- LOGD("Pointer down received while already down.");
+ LOGD("Conflicting pointer actions: Down received while already down.");
#endif
+ *outConflictingPointerActions = true;
}
mTouchState.copyFrom(mTempTouchState);
} else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
@@ -1868,6 +1886,18 @@
return;
}
+ // If the motion event was modified in flight, then we cannot stream the sample.
+ if ((motionEventDispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_MASK)
+ != InputTarget::FLAG_DISPATCH_AS_IS) {
+#if DEBUG_BATCHING
+ LOGD("channel '%s' ~ Not streaming because the motion event was not "
+ "being dispatched as-is. "
+ "(Waiting for next dispatch cycle to start.)",
+ connection->getInputChannelName());
+#endif
+ return;
+ }
+
// The dispatch entry is in progress and is still potentially open for streaming.
// Try to stream the new motion sample. This might fail if the consumer has already
// consumed the motion event (or if the channel is broken).
@@ -1972,6 +2002,66 @@
dispatchEntry->headMotionSample = appendedMotionSample;
}
+ // Apply target flags and update the connection's input state.
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+ dispatchEntry->resolvedAction = keyEntry->action;
+ dispatchEntry->resolvedFlags = keyEntry->flags;
+
+ if (!connection->inputState.trackKey(keyEntry,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
+ connection->getInputChannelName());
+#endif
+ return; // skip the inconsistent event
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+ if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
+ } else {
+ dispatchEntry->resolvedAction = motionEntry->action;
+ }
+ if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+ && !connection->inputState.isHovering(
+ motionEntry->deviceId, motionEntry->source)) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover enter event",
+ connection->getInputChannelName());
+#endif
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ }
+
+ dispatchEntry->resolvedFlags = motionEntry->flags;
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+ dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ if (!connection->inputState.trackMotion(motionEntry,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion event",
+ connection->getInputChannelName());
+#endif
+ return; // skip the inconsistent event
+ }
+ break;
+ }
+ }
+
// Enqueue the dispatch entry.
connection->outboundQueue.enqueueAtTail(dispatchEntry);
}
@@ -1999,16 +2089,11 @@
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
- // Apply target flags.
- int32_t action = keyEntry->action;
- int32_t flags = keyEntry->flags;
-
- // Update the connection's input state.
- connection->inputState.trackKey(keyEntry, action);
-
// Publish the key event.
- status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
- action, flags, keyEntry->keyCode, keyEntry->scanCode,
+ status = connection->inputPublisher.publishKeyEvent(
+ keyEntry->deviceId, keyEntry->source,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+ keyEntry->keyCode, keyEntry->scanCode,
keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
keyEntry->eventTime);
@@ -2024,24 +2109,6 @@
case EventEntry::TYPE_MOTION: {
MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
- // Apply target flags.
- int32_t action = motionEntry->action;
- int32_t flags = motionEntry->flags;
- if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
- action = AMOTION_EVENT_ACTION_OUTSIDE;
- } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
- action = AMOTION_EVENT_ACTION_HOVER_EXIT;
- } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
- action = AMOTION_EVENT_ACTION_HOVER_ENTER;
- } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
- action = AMOTION_EVENT_ACTION_CANCEL;
- } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
- action = AMOTION_EVENT_ACTION_DOWN;
- }
- if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
- flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
- }
-
// If headMotionSample is non-NULL, then it points to the first new sample that we
// were unable to dispatch during the previous cycle so we resume dispatching from
// that point in the list of motion samples.
@@ -2082,13 +2149,11 @@
}
}
- // Update the connection's input state.
- connection->inputState.trackMotion(motionEntry, action);
-
// Publish the motion event and the first motion sample.
- status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
- motionEntry->source, action, flags, motionEntry->edgeFlags,
- motionEntry->metaState, motionEntry->buttonState,
+ status = connection->inputPublisher.publishMotionEvent(
+ motionEntry->deviceId, motionEntry->source,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+ motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState,
xOffset, yOffset,
motionEntry->xPrecision, motionEntry->yPrecision,
motionEntry->downTime, firstMotionSample->eventTime,
@@ -2102,8 +2167,8 @@
return;
}
- if (action == AMOTION_EVENT_ACTION_MOVE
- || action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_MOVE
+ || dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
// Append additional motion samples.
MotionSample* nextMotionSample = firstMotionSample->next;
for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
@@ -2355,23 +2420,22 @@
break;
}
- int32_t xOffset, yOffset;
- float scaleFactor;
+ InputTarget target;
const InputWindow* window = getWindowLocked(connection->inputChannel);
if (window) {
- xOffset = -window->frameLeft;
- yOffset = -window->frameTop;
- scaleFactor = window->scaleFactor;
+ target.xOffset = -window->frameLeft;
+ target.yOffset = -window->frameTop;
+ target.scaleFactor = window->scaleFactor;
} else {
- xOffset = 0;
- yOffset = 0;
- scaleFactor = 1.0f;
+ target.xOffset = 0;
+ target.yOffset = 0;
+ target.scaleFactor = 1.0f;
}
+ target.inputChannel = connection->inputChannel;
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
- DispatchEntry* cancelationDispatchEntry =
- mAllocator.obtainDispatchEntry(cancelationEventEntry, // increments ref
- 0, xOffset, yOffset, scaleFactor);
- connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+ enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
+ &target, false, InputTarget::FLAG_DISPATCH_AS_IS);
mAllocator.releaseEventEntry(cancelationEventEntry);
}
@@ -3327,6 +3391,7 @@
resetTargetsLocked();
mTouchState.reset();
+ mLastHoverWindow = NULL;
}
void InputDispatcher::logDispatchStateLocked() {
@@ -4125,111 +4190,180 @@
return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
}
-void InputDispatcher::InputState::trackEvent(const EventEntry* entry, int32_t action) {
- switch (entry->type) {
- case EventEntry::TYPE_KEY:
- trackKey(static_cast<const KeyEntry*>(entry), action);
- break;
+bool InputDispatcher::InputState::isHovering(int32_t deviceId, uint32_t source) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.deviceId == deviceId
+ && memento.source == source
+ && memento.hovering) {
+ return true;
+ }
+ }
+ return false;
+}
- case EventEntry::TYPE_MOTION:
- trackMotion(static_cast<const MotionEntry*>(entry), action);
- break;
+bool InputDispatcher::InputState::trackKey(const KeyEntry* entry,
+ int32_t action, int32_t flags) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_UP: {
+ if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) {
+ for (size_t i = 0; i < mFallbackKeys.size(); ) {
+ if (mFallbackKeys.valueAt(i) == entry->keyCode) {
+ mFallbackKeys.removeItemsAt(i);
+ } else {
+ i += 1;
+ }
+ }
+ }
+ ssize_t index = findKeyMemento(entry);
+ if (index >= 0) {
+ mKeyMementos.removeAt(index);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, "
+ "keyCode=%d, scanCode=%d",
+ entry->deviceId, entry->source, entry->keyCode, entry->scanCode);
+#endif
+ return false;
+ }
+
+ case AKEY_EVENT_ACTION_DOWN: {
+ ssize_t index = findKeyMemento(entry);
+ if (index >= 0) {
+ mKeyMementos.removeAt(index);
+ }
+ addKeyMemento(entry, flags);
+ return true;
+ }
+
+ default:
+ return true;
}
}
-void InputDispatcher::InputState::trackKey(const KeyEntry* entry, int32_t action) {
- if (action == AKEY_EVENT_ACTION_UP
- && (entry->flags & AKEY_EVENT_FLAG_FALLBACK)) {
- for (size_t i = 0; i < mFallbackKeys.size(); ) {
- if (mFallbackKeys.valueAt(i) == entry->keyCode) {
- mFallbackKeys.removeItemsAt(i);
- } else {
- i += 1;
- }
+bool InputDispatcher::InputState::trackMotion(const MotionEntry* entry,
+ int32_t action, int32_t flags) {
+ int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+ switch (actionMasked) {
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ return true;
}
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, "
+ "actionMasked=%d",
+ entry->deviceId, entry->source, actionMasked);
+#endif
+ return false;
}
+ case AMOTION_EVENT_ACTION_DOWN: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ }
+ addMotionMemento(entry, flags, false /*hovering*/);
+ return true;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_MOVE: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ MotionMemento& memento = mMotionMementos.editItemAt(index);
+ memento.setPointers(entry);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("Dropping inconsistent motion pointer up/down or move event: "
+ "deviceId=%d, source=%08x, actionMasked=%d",
+ entry->deviceId, entry->source, actionMasked);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+ ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x",
+ entry->deviceId, entry->source);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE: {
+ ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ }
+ addMotionMemento(entry, flags, true /*hovering*/);
+ return true;
+ }
+
+ default:
+ return true;
+ }
+}
+
+ssize_t InputDispatcher::InputState::findKeyMemento(const KeyEntry* entry) const {
for (size_t i = 0; i < mKeyMementos.size(); i++) {
- KeyMemento& memento = mKeyMementos.editItemAt(i);
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
if (memento.deviceId == entry->deviceId
&& memento.source == entry->source
&& memento.keyCode == entry->keyCode
&& memento.scanCode == entry->scanCode) {
- switch (action) {
- case AKEY_EVENT_ACTION_UP:
- mKeyMementos.removeAt(i);
- return;
-
- case AKEY_EVENT_ACTION_DOWN:
- mKeyMementos.removeAt(i);
- goto Found;
-
- default:
- return;
- }
+ return i;
}
}
-
-Found:
- if (action == AKEY_EVENT_ACTION_DOWN) {
- mKeyMementos.push();
- KeyMemento& memento = mKeyMementos.editTop();
- memento.deviceId = entry->deviceId;
- memento.source = entry->source;
- memento.keyCode = entry->keyCode;
- memento.scanCode = entry->scanCode;
- memento.flags = entry->flags;
- memento.downTime = entry->downTime;
- }
+ return -1;
}
-void InputDispatcher::InputState::trackMotion(const MotionEntry* entry, int32_t action) {
- int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+ssize_t InputDispatcher::InputState::findMotionMemento(const MotionEntry* entry,
+ bool hovering) const {
for (size_t i = 0; i < mMotionMementos.size(); i++) {
- MotionMemento& memento = mMotionMementos.editItemAt(i);
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
if (memento.deviceId == entry->deviceId
- && memento.source == entry->source) {
- switch (actionMasked) {
- case AMOTION_EVENT_ACTION_UP:
- case AMOTION_EVENT_ACTION_CANCEL:
- case AMOTION_EVENT_ACTION_HOVER_ENTER:
- case AMOTION_EVENT_ACTION_HOVER_MOVE:
- case AMOTION_EVENT_ACTION_HOVER_EXIT:
- mMotionMementos.removeAt(i);
- return;
-
- case AMOTION_EVENT_ACTION_DOWN:
- mMotionMementos.removeAt(i);
- goto Found;
-
- case AMOTION_EVENT_ACTION_POINTER_UP:
- case AMOTION_EVENT_ACTION_POINTER_DOWN:
- case AMOTION_EVENT_ACTION_MOVE:
- memento.setPointers(entry);
- return;
-
- default:
- return;
- }
+ && memento.source == entry->source
+ && memento.hovering == hovering) {
+ return i;
}
}
+ return -1;
+}
-Found:
- switch (actionMasked) {
- case AMOTION_EVENT_ACTION_DOWN:
- case AMOTION_EVENT_ACTION_HOVER_ENTER:
- case AMOTION_EVENT_ACTION_HOVER_MOVE:
- case AMOTION_EVENT_ACTION_HOVER_EXIT:
- mMotionMementos.push();
- MotionMemento& memento = mMotionMementos.editTop();
- memento.deviceId = entry->deviceId;
- memento.source = entry->source;
- memento.xPrecision = entry->xPrecision;
- memento.yPrecision = entry->yPrecision;
- memento.downTime = entry->downTime;
- memento.setPointers(entry);
- memento.hovering = actionMasked != AMOTION_EVENT_ACTION_DOWN;
- }
+void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) {
+ mKeyMementos.push();
+ KeyMemento& memento = mKeyMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.keyCode = entry->keyCode;
+ memento.scanCode = entry->scanCode;
+ memento.flags = flags;
+ memento.downTime = entry->downTime;
+}
+
+void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
+ int32_t flags, bool hovering) {
+ mMotionMementos.push();
+ MotionMemento& memento = mMotionMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.flags = flags;
+ memento.xPrecision = entry->xPrecision;
+ memento.yPrecision = entry->yPrecision;
+ memento.downTime = entry->downTime;
+ memento.setPointers(entry);
+ memento.hovering = hovering;
}
void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
@@ -4243,20 +4377,17 @@
void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
Allocator* allocator, Vector<EventEntry*>& outEvents,
const CancelationOptions& options) {
- for (size_t i = 0; i < mKeyMementos.size(); ) {
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
const KeyMemento& memento = mKeyMementos.itemAt(i);
if (shouldCancelKey(memento, options)) {
outEvents.push(allocator->obtainKeyEntry(currentTime,
memento.deviceId, memento.source, 0,
AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
- mKeyMementos.removeAt(i);
- } else {
- i += 1;
}
}
- for (size_t i = 0; i < mMotionMementos.size(); ) {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
const MotionMemento& memento = mMotionMementos.itemAt(i);
if (shouldCancelMotion(memento, options)) {
outEvents.push(allocator->obtainMotionEntry(currentTime,
@@ -4264,12 +4395,9 @@
memento.hovering
? AMOTION_EVENT_ACTION_HOVER_EXIT
: AMOTION_EVENT_ACTION_CANCEL,
- 0, 0, 0, 0,
+ memento.flags, 0, 0, 0,
memento.xPrecision, memento.yPrecision, memento.downTime,
memento.pointerCount, memento.pointerProperties, memento.pointerCoords));
- mMotionMementos.removeAt(i);
- } else {
- i += 1;
}
}
}
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 676d162..bdd1922 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -522,6 +522,10 @@
// True if dispatch has started.
bool inProgress;
+ // Set to the resolved action and flags when the event is enqueued.
+ int32_t resolvedAction;
+ int32_t resolvedFlags;
+
// For motion events:
// Pointer to the first motion sample to dispatch in this cycle.
// Usually NULL to indicate that the list of motion samples begins at
@@ -709,14 +713,19 @@
// Returns true if there is no state to be canceled.
bool isNeutral() const;
- // Records tracking information for an event that has just been published.
- void trackEvent(const EventEntry* entry, int32_t action);
+ // Returns true if the specified source is known to have received a hover enter
+ // motion event.
+ bool isHovering(int32_t deviceId, uint32_t source) const;
// Records tracking information for a key event that has just been published.
- void trackKey(const KeyEntry* entry, int32_t action);
+ // Returns true if the event should be delivered, false if it is inconsistent
+ // and should be skipped.
+ bool trackKey(const KeyEntry* entry, int32_t action, int32_t flags);
// Records tracking information for a motion event that has just been published.
- void trackMotion(const MotionEntry* entry, int32_t action);
+ // Returns true if the event should be delivered, false if it is inconsistent
+ // and should be skipped.
+ bool trackMotion(const MotionEntry* entry, int32_t action, int32_t flags);
// Synthesizes cancelation events for the current state and resets the tracked state.
void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator,
@@ -756,6 +765,7 @@
struct MotionMemento {
int32_t deviceId;
uint32_t source;
+ int32_t flags;
float xPrecision;
float yPrecision;
nsecs_t downTime;
@@ -771,6 +781,12 @@
Vector<MotionMemento> mMotionMementos;
KeyedVector<int32_t, int32_t> mFallbackKeys;
+ ssize_t findKeyMemento(const KeyEntry* entry) const;
+ ssize_t findMotionMemento(const MotionEntry* entry, bool hovering) const;
+
+ void addKeyMemento(const KeyEntry* entry, int32_t flags);
+ void addMotionMemento(const MotionEntry* entry, int32_t flags, bool hovering);
+
static bool shouldCancelKey(const KeyMemento& memento,
const CancelationOptions& options);
static bool shouldCancelMotion(const MotionMemento& memento,
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 5a25f8c..014f962 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -53,6 +53,7 @@
#define INDENT2 " "
#define INDENT3 " "
#define INDENT4 " "
+#define INDENT5 " "
namespace android {
@@ -1921,8 +1922,17 @@
dump.appendFormat(INDENT4 "DistanceScale: %0.3f\n", mLocked.distanceScale);
dump.appendFormat(INDENT3 "Last Touch:\n");
- dump.appendFormat(INDENT4 "Pointer Count: %d\n", mLastTouch.pointerCount);
dump.appendFormat(INDENT4 "Button State: 0x%08x\n", mLastTouch.buttonState);
+ dump.appendFormat(INDENT4 "Pointer Count: %d\n", mLastTouch.pointerCount);
+ for (uint32_t i = 0; i < mLastTouch.pointerCount; i++) {
+ const PointerData& pointer = mLastTouch.pointers[i];
+ dump.appendFormat(INDENT5 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
+ "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
+ "orientation=%d, distance=%d, isStylus=%s\n", i,
+ pointer.id, pointer.x, pointer.y, pointer.pressure,
+ pointer.touchMajor, pointer.touchMinor, pointer.toolMajor, pointer.toolMinor,
+ pointer.orientation, pointer.distance, toString(pointer.isStylus));
+ }
if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
dump.appendFormat(INDENT3 "Pointer Gesture Detector:\n");
@@ -3704,6 +3714,29 @@
mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
mPointerGesture.currentGestureIdBits, -1,
0, 0, mPointerGesture.downTime);
+ } else if (dispatchedGestureIdBits.isEmpty()
+ && !mPointerGesture.lastGestureIdBits.isEmpty()) {
+ // Synthesize a hover move event after all pointers go up to indicate that
+ // the pointer is hovering again even if the user is not currently touching
+ // the touch pad. This ensures that a view will receive a fresh hover enter
+ // event after a tap.
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+
+ getDispatcher()->notifyMotion(when, getDeviceId(), mPointerSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ 1, &pointerProperties, &pointerCoords, 0, 0, mPointerGesture.downTime);
}
// Update state.
diff --git a/services/java/com/android/server/DnsPinger.java b/services/java/com/android/server/DnsPinger.java
index 05de53a..a7324d9 100644
--- a/services/java/com/android/server/DnsPinger.java
+++ b/services/java/com/android/server/DnsPinger.java
@@ -20,7 +20,9 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
+import android.net.NetworkUtils;
import android.os.SystemClock;
+import android.provider.Settings;
import android.util.Slog;
import java.net.DatagramPacket;
@@ -38,7 +40,7 @@
* API may not differentiate between a time out and a failure lookup (which we
* really care about).
* <p>
- * TODO : More general API. Socket does not bind to specified connection type
+ * TODO : More general API. Socket does not bind to specified connection type
* TODO : Choice of DNS query location - current looks up www.android.com
*
* @hide
@@ -56,44 +58,65 @@
private static Random sRandom = new Random();
private ConnectivityManager mConnectivityManager = null;
- private ContentResolver mContentResolver;
private Context mContext;
private int mConnectionType;
+ private InetAddress mDefaultDns;
private String TAG;
-
/**
- * @param connectionType The connection type from @link {@link ConnectivityManager}
+ * @param connectionType The connection type from {@link ConnectivityManager}
*/
public DnsPinger(String TAG, Context context, int connectionType) {
mContext = context;
- mContentResolver = context.getContentResolver();
mConnectionType = connectionType;
+ if (!ConnectivityManager.isNetworkTypeValid(connectionType)) {
+ Slog.e(TAG, "Invalid connectionType in constructor: " + connectionType);
+ }
this.TAG = TAG;
+
+ mDefaultDns = getDefaultDns();
}
/**
- * @return The first DNS in the link properties of the specified connection type
+ * @return The first DNS in the link properties of the specified connection
+ * type or the default system DNS if the link properties has null
+ * dns set. Should not be null.
*/
public InetAddress getDns() {
- LinkProperties linkProperties = getCurLinkProperties();
- if (linkProperties == null)
- return null;
-
- Collection<InetAddress> dnses = linkProperties.getDnses();
- if (dnses == null || dnses.size() == 0)
- return null;
-
- return dnses.iterator().next();
- }
-
- private LinkProperties getCurLinkProperties() {
if (mConnectivityManager == null) {
mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
}
- return mConnectivityManager.getLinkProperties(mConnectionType);
+
+ LinkProperties curLinkProps = mConnectivityManager.getLinkProperties(mConnectionType);
+ if (curLinkProps == null) {
+ Slog.e(TAG, "getCurLinkProperties:: LP for type" + mConnectionType + " is null!");
+ return mDefaultDns;
+ }
+
+ Collection<InetAddress> dnses = curLinkProps.getDnses();
+ if (dnses == null || dnses.size() == 0) {
+ Slog.v(TAG, "getDns::LinkProps has null dns - returning default");
+ return mDefaultDns;
+ }
+
+ return dnses.iterator().next();
+ }
+
+ private InetAddress getDefaultDns() {
+ String dns = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.DEFAULT_DNS_SERVER);
+ if (dns == null || dns.length() == 0) {
+ dns = mContext.getResources().getString(
+ com.android.internal.R.string.config_default_dns_server);
+ }
+ try {
+ return NetworkUtils.numericToInetAddress(dns);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "getDefaultDns::malformed default dns address");
+ return null;
+ }
}
/**
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index b03bd4d..14abf80 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1641,7 +1641,7 @@
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
if (imi == null) return false;
final int N = subtypes.length;
- mFileManager.addInputMethodSubtypes(mCurMethodId, subtypes);
+ mFileManager.addInputMethodSubtypes(imi, subtypes);
buildInputMethodListLocked(mMethodList, mMethodMap);
return true;
}
@@ -2023,25 +2023,26 @@
final CharSequence label = imi.loadLabel(pm);
if (showSubtypes && enabledSubtypeSet.size() > 0) {
final int subtypeCount = imi.getSubtypeCount();
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
+ }
for (int j = 0; j < subtypeCount; ++j) {
- InputMethodSubtype subtype = imi.getSubtypeAt(j);
- if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))
- && !subtype.isAuxiliary()) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+ final String subtypeHashCode = String.valueOf(subtype.hashCode());
+ // We show all enabled IMEs and subtypes when an IME is shown.
+ if (enabledSubtypeSet.contains(subtypeHashCode)
+ && (mInputShown || !subtype.isAuxiliary())) {
final CharSequence title;
- int nameResId = subtype.getNameResId();
- String mode = subtype.getMode();
- if (nameResId != 0) {
- title = TextUtils.concat(subtype.getDisplayName(context,
- imi.getPackageName(), imi.getServiceInfo().applicationInfo),
- (TextUtils.isEmpty(label) ? "" : " (" + label + ")"));
- } else {
- CharSequence language = subtype.getLocale();
- // TODO: Use more friendly Title and UI
- title = label + "," + (mode == null ? "" : mode) + ","
- + (language == null ? "" : language);
- }
+ final String mode = subtype.getMode();
+ title = TextUtils.concat(subtype.getDisplayName(context,
+ imi.getPackageName(), imi.getServiceInfo().applicationInfo),
+ (TextUtils.isEmpty(label) ? "" : " (" + label + ")"));
imList.add(new Pair<CharSequence, Pair<InputMethodInfo, Integer>>(
title, new Pair<InputMethodInfo, Integer>(imi, j)));
+ // Removing this subtype from enabledSubtypeSet because we no longer
+ // need to add an entry of this subtype to imList to avoid duplicated
+ // entries.
+ enabledSubtypeSet.remove(subtypeHashCode);
}
}
} else {
@@ -2338,7 +2339,7 @@
}
}
}
- ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
+ final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
applicableModeAndSubtypesMap.values());
if (!containsKeyboardSubtype) {
InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
@@ -3016,17 +3017,23 @@
}
public void addInputMethodSubtypes(
- String imiId, InputMethodSubtype[] additionalSubtypes) {
+ InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
synchronized (mMethodMap) {
+ final HashSet<InputMethodSubtype> existingSubtypes =
+ new HashSet<InputMethodSubtype>();
+ for (int i = 0; i < imi.getSubtypeCount(); ++i) {
+ existingSubtypes.add(imi.getSubtypeAt(i));
+ }
+
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
final int N = additionalSubtypes.length;
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = additionalSubtypes[i];
- if (!subtypes.contains(subtype)) {
+ if (!subtypes.contains(subtype) && !existingSubtypes.contains(subtype)) {
subtypes.add(subtype);
}
}
- mSubtypesMap.put(imiId, subtypes);
+ mSubtypesMap.put(imi.getId(), subtypes);
writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
mMethodMap);
}
@@ -3133,8 +3140,8 @@
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
final String imeSubtypeExtraValue =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
- final boolean isAuxiliary =
- Boolean.valueOf(parser.getAttributeValue(null, ATTR_IS_AUXILIARY));
+ final boolean isAuxiliary = "1".equals(String.valueOf(
+ parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
final InputMethodSubtype subtype =
new InputMethodSubtype(label, icon, imeSubtypeLocale,
imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index e730d0d..b31d128 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -38,6 +38,9 @@
import android.util.Log;
import android.util.Slog;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
@@ -48,6 +51,7 @@
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.concurrent.CountDownLatch;
@@ -65,9 +69,18 @@
private static final int ADD = 1;
private static final int REMOVE = 2;
+ /** Path to {@code /proc/uid_stat}. */
@Deprecated
- private static final File STATS_UIDSTAT = new File("/proc/uid_stat");
- private static final File STATS_NETFILTER = new File("/proc/net/xt_qtaguid/stats");
+ private final File mProcStatsUidstat;
+ /** Path to {@code /proc/net/xt_qtaguid/stats}. */
+ private final File mProcStatsNetfilter;
+
+ /** {@link #mProcStatsNetfilter} headers. */
+ private static final String KEY_IFACE = "iface";
+ private static final String KEY_TAG_HEX = "acct_tag_hex";
+ private static final String KEY_UID = "uid_tag_int";
+ private static final String KEY_RX = "rx_bytes";
+ private static final String KEY_TX = "tx_bytes";
class NetdResponseCode {
public static final int InterfaceListResult = 110;
@@ -107,10 +120,13 @@
*
* @param context Binder context for this service
*/
- private NetworkManagementService(Context context) {
+ private NetworkManagementService(Context context, File procRoot) {
mContext = context;
mObservers = new ArrayList<INetworkManagementEventObserver>();
+ mProcStatsUidstat = new File(procRoot, "uid_stat");
+ mProcStatsNetfilter = new File(procRoot, "net/xt_qtaguid/stats");
+
if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
return;
}
@@ -121,7 +137,8 @@
}
public static NetworkManagementService create(Context context) throws InterruptedException {
- NetworkManagementService service = new NetworkManagementService(context);
+ NetworkManagementService service = new NetworkManagementService(
+ context, new File("/proc/"));
if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
service.mThread.start();
if (DBG) Slog.d(TAG, "Awaiting socket connection");
@@ -130,6 +147,12 @@
return service;
}
+ // @VisibleForTesting
+ public static NetworkManagementService createForTest(Context context, File procRoot) {
+ // TODO: eventually connect with mock netd
+ return new NetworkManagementService(context, procRoot);
+ }
+
public void registerObserver(INetworkManagementEventObserver obs) {
Slog.d(TAG, "Registering observer");
mObservers.add(obs);
@@ -888,7 +911,7 @@
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
- if (STATS_NETFILTER.exists()) {
+ if (mProcStatsNetfilter.exists()) {
return getNetworkStatsDetailNetfilter(UID_ALL);
} else {
return getNetworkStatsDetailUidstat(UID_ALL);
@@ -902,7 +925,7 @@
android.Manifest.permission.ACCESS_NETWORK_STATE, "NetworkManagementService");
}
- if (STATS_NETFILTER.exists()) {
+ if (mProcStatsNetfilter.exists()) {
return getNetworkStatsDetailNetfilter(uid);
} else {
return getNetworkStatsDetailUidstat(uid);
@@ -914,35 +937,36 @@
*/
private NetworkStats getNetworkStatsDetailNetfilter(int limitUid) {
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
+ final ArrayList<String> keys = Lists.newArrayList();
+ final ArrayList<String> values = Lists.newArrayList();
+ final HashMap<String, String> parsed = Maps.newHashMap();
BufferedReader reader = null;
try {
- reader = new BufferedReader(new FileReader(STATS_NETFILTER));
+ reader = new BufferedReader(new FileReader(mProcStatsNetfilter));
- // assumes format from kernel:
- // idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
-
- // skip first line, which is legend
+ // parse first line as header
String line = reader.readLine();
- while ((line = reader.readLine()) != null) {
- final StringTokenizer t = new StringTokenizer(line);
+ splitLine(line, keys);
- final String idx = t.nextToken();
- final String iface = t.nextToken();
+ // parse remaining lines
+ while ((line = reader.readLine()) != null) {
+ splitLine(line, values);
+ parseLine(keys, values, parsed);
try {
- // TODO: kernel currently emits tag in upper half of long;
- // eventually switch to directly using int.
- final int tag = (int) (Long.parseLong(t.nextToken().substring(2), 16) >> 32);
- final int uid = Integer.parseInt(t.nextToken());
- final long rx = Long.parseLong(t.nextToken());
- final long tx = Long.parseLong(t.nextToken());
+ final String iface = parsed.get(KEY_IFACE);
+ final int tag = NetworkManagementSocketTagger.kernelToTag(
+ parsed.get(KEY_TAG_HEX));
+ final int uid = Integer.parseInt(parsed.get(KEY_UID));
+ final long rx = Long.parseLong(parsed.get(KEY_RX));
+ final long tx = Long.parseLong(parsed.get(KEY_TX));
if (limitUid == UID_ALL || limitUid == uid) {
stats.addEntry(iface, uid, tag, rx, tx);
}
} catch (NumberFormatException e) {
- Slog.w(TAG, "problem parsing stats for idx " + idx + ": " + e);
+ Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
}
}
} catch (IOException e) {
@@ -964,7 +988,7 @@
private NetworkStats getNetworkStatsDetailUidstat(int limitUid) {
final String[] knownUids;
if (limitUid == UID_ALL) {
- knownUids = STATS_UIDSTAT.list();
+ knownUids = mProcStatsUidstat.list();
} else {
knownUids = new String[] { String.valueOf(limitUid) };
}
@@ -973,7 +997,7 @@
SystemClock.elapsedRealtime(), knownUids.length);
for (String uid : knownUids) {
final int uidInt = Integer.parseInt(uid);
- final File uidPath = new File(STATS_UIDSTAT, uid);
+ final File uidPath = new File(mProcStatsUidstat, uid);
final long rx = readSingleLongFromFile(new File(uidPath, "tcp_rcv"));
final long tx = readSingleLongFromFile(new File(uidPath, "tcp_snd"));
stats.addEntry(IFACE_ALL, uidInt, TAG_NONE, rx, tx);
@@ -1048,6 +1072,32 @@
}
/**
+ * Split given line into {@link ArrayList}.
+ */
+ private static void splitLine(String line, ArrayList<String> outSplit) {
+ outSplit.clear();
+
+ final StringTokenizer t = new StringTokenizer(line);
+ while (t.hasMoreTokens()) {
+ outSplit.add(t.nextToken());
+ }
+ }
+
+ /**
+ * Zip the two given {@link ArrayList} as key and value pairs into
+ * {@link HashMap}.
+ */
+ private static void parseLine(
+ ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
+ outParsed.clear();
+
+ final int size = Math.min(keys.size(), values.size());
+ for (int i = 0; i < size; i++) {
+ outParsed.put(keys.get(i), values.get(i));
+ }
+ }
+
+ /**
* Utility method to read a single plain-text {@link Long} from the given
* {@link File}, usually from a {@code /proc/} filesystem.
*/
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 3ba9c14..1356e2a 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -95,14 +95,14 @@
private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000;
private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000;
- private static final int MAX_CHECKS_PER_SSID = 7;
- private static final int NUM_DNS_PINGS = 5;
+ private static final int MAX_CHECKS_PER_SSID = 9;
+ private static final int NUM_DNS_PINGS = 7;
private static double MIN_RESPONSE_RATE = 0.50;
// TODO : Adjust multiple DNS downward to 250 on repeated failure
// private static final int MULTI_DNS_PING_TIMEOUT_MS = 250;
- private static final int DNS_PING_TIMEOUT_MS = 1000;
+ private static final int DNS_PING_TIMEOUT_MS = 800;
private static final long DNS_PING_INTERVAL = 250;
private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000;
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 584cd03..12d3ed8 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -72,6 +72,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IPowerManager;
+import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.telephony.TelephonyManager;
@@ -148,6 +149,9 @@
private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS;
+ private static final int MSG_RULES_CHANGED = 0x1;
+ private static final int MSG_METERED_IFACES_CHANGED = 0x2;
+
private final Context mContext;
private final IActivityManager mActivityManager;
private final IPowerManager mPowerManager;
@@ -210,7 +214,7 @@
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
+ mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
}
@@ -269,9 +273,6 @@
// only someone like AMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
- // skip when UID couldn't have any policy
- if (!isUidValidForPolicy(mContext, uid)) return;
-
synchronized (mRulesLock) {
// because a uid can have multiple pids running inside, we need to
// remember all pid states and summarize foreground at uid level.
@@ -292,9 +293,6 @@
// only someone like AMS should only be calling us
mContext.enforceCallingOrSelfPermission(MANAGE_APP_TOKENS, TAG);
- // skip when UID couldn't have any policy
- if (!isUidValidForPolicy(mContext, uid)) return;
-
synchronized (mRulesLock) {
// clear records and recompute, when they exist
final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
@@ -599,20 +597,8 @@
}
}
- // dispatch changed rule to existing listeners
- // TODO: dispatch outside of holding lock
final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
- final int length = mListeners.beginBroadcast();
- for (int i = 0; i < length; i++) {
- final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
- if (listener != null) {
- try {
- listener.onMeteredIfacesChanged(meteredIfaces);
- } catch (RemoteException e) {
- }
- }
- }
- mListeners.finishBroadcast();
+ mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget();
}
/**
@@ -804,32 +790,7 @@
mListeners.register(listener);
- synchronized (mRulesLock) {
- // dispatch any existing rules to new listeners
- // TODO: dispatch outside of holding lock
- final int size = mUidRules.size();
- for (int i = 0; i < size; i++) {
- final int uid = mUidRules.keyAt(i);
- final int uidRules = mUidRules.valueAt(i);
- if (uidRules != RULE_ALLOW_ALL) {
- try {
- listener.onUidRulesChanged(uid, uidRules);
- } catch (RemoteException e) {
- }
- }
- }
-
- // dispatch any metered ifaces to new listeners
- // TODO: dispatch outside of holding lock
- if (mMeteredIfaces.size() > 0) {
- final String[] meteredIfaces = mMeteredIfaces.toArray(
- new String[mMeteredIfaces.size()]);
- try {
- listener.onMeteredIfacesChanged(meteredIfaces);
- } catch (RemoteException e) {
- }
- }
- }
+ // TODO: consider dispatching existing rules to new listeners
}
@Override
@@ -978,8 +939,6 @@
}
private void updateRulesForUidLocked(int uid) {
- if (!isUidValidForPolicy(mContext, uid)) return;
-
final int uidPolicy = getUidPolicy(uid);
final boolean uidForeground = isUidForeground(uid);
@@ -999,19 +958,50 @@
//kernelSetUidRejectPaid(uid, rejectPaid);
// dispatch changed rule to existing listeners
- // TODO: dispatch outside of holding lock
- final int length = mListeners.beginBroadcast();
- for (int i = 0; i < length; i++) {
- final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
- if (listener != null) {
- try {
- listener.onUidRulesChanged(uid, uidRules);
- } catch (RemoteException e) {
+ mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules).sendToTarget();
+ }
+
+ private Handler.Callback mHandlerCallback = new Handler.Callback() {
+ /** {@inheritDoc} */
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_RULES_CHANGED: {
+ final int uid = msg.arg1;
+ final int uidRules = msg.arg2;
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ if (listener != null) {
+ try {
+ listener.onUidRulesChanged(uid, uidRules);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ mListeners.finishBroadcast();
+ return true;
+ }
+ case MSG_METERED_IFACES_CHANGED: {
+ final String[] meteredIfaces = (String[]) msg.obj;
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ if (listener != null) {
+ try {
+ listener.onMeteredIfacesChanged(meteredIfaces);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ mListeners.finishBroadcast();
+ return true;
+ }
+ default: {
+ return false;
}
}
}
- mListeners.finishBroadcast();
- }
+ };
private String getActiveSubscriberId() {
final TelephonyManager telephony = (TelephonyManager) mContext.getSystemService(
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 4a79d17..7610a11 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -124,8 +124,6 @@
private PendingIntent mPollIntent;
// TODO: listen for kernel push events through netd instead of polling
- // TODO: watch for UID uninstall, and transfer stats into single bucket
-
// TODO: trim empty history objects entirely
private static final long KB_IN_BYTES = 1024;
@@ -506,8 +504,11 @@
try {
networkSnapshot = mNetworkManager.getNetworkStatsSummary();
uidSnapshot = detailedPoll ? mNetworkManager.getNetworkStatsDetail() : null;
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "problem reading network stats: " + e);
+ return;
} catch (RemoteException e) {
- Slog.w(TAG, "problem reading network stats");
+ Slog.w(TAG, "problem reading network stats: " + e);
return;
}
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_extended b/services/tests/servicestests/res/raw/xt_qtaguid_extended
new file mode 100644
index 0000000..5bef3dd
--- /dev/null
+++ b/services/tests/servicestests/res/raw/xt_qtaguid_extended
@@ -0,0 +1,3 @@
+acct_tag_hex uid_tag_int iface rx_bytes rx_packets tx_bytes tx_packets teleported_goats
+0x0 1000 test0 1024 10 2048 20 2716057
+0x0000F00D00000000 1000 test0 512 5 512 5 3370318
diff --git a/services/tests/servicestests/res/raw/xt_qtaguid_typical b/services/tests/servicestests/res/raw/xt_qtaguid_typical
new file mode 100644
index 0000000..7c4f04e
--- /dev/null
+++ b/services/tests/servicestests/res/raw/xt_qtaguid_typical
@@ -0,0 +1,32 @@
+idx iface acct_tag_hex uid_tag_int rx_bytes tx_bytes
+1 wlan0 0x0 0 14615 4270
+2 wlan0 0x0 1000 5175 915
+3 wlan0 0x0 1021 3381 903
+4 wlan0 0x0 10004 333821 53558
+5 wlan0 0x0 10010 4888 37363
+6 wlan0 0x0 10013 52 104
+7 wlan0 0x74182ada00000000 10004 18725 1066
+8 rmnet0 0x0 0 301274 30244
+9 rmnet0 0x0 1000 304 441
+10 rmnet0 0x0 1013 2880 2272
+11 rmnet0 0x0 1021 31407 8430
+12 rmnet0 0x0 10003 32665 3814
+13 rmnet0 0x0 10004 2373141 420112
+14 rmnet0 0x0 10010 870370 1111727
+15 rmnet0 0x0 10013 240 240
+16 rmnet0 0x0 10016 16703 13512
+17 rmnet0 0x0 10017 3990 3269
+18 rmnet0 0x0 10018 474504 14516062
+19 rmnet0 0x0 10019 782804 71077
+20 rmnet0 0x0 10022 70671 49684
+21 rmnet0 0x0 10029 5785354 397159
+22 rmnet0 0x0 10033 2102 1686
+23 rmnet0 0x0 10034 15495464 227694
+24 rmnet0 0x0 10037 31184994 684122
+25 rmnet0 0x0 10051 298687 113485
+26 rmnet0 0x0 10056 29504 20669
+27 rmnet0 0x0 10069 683 596
+28 rmnet0 0x0 10072 34051 12453
+29 rmnet0 0x0 10077 7025393 213866
+30 rmnet0 0x0 10081 354 1178
+31 rmnet0 0x74182ada00000000 10037 28507378 437004
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 0000000..ac7cb5a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 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 com.android.server;
+
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+import static com.android.server.NetworkManagementSocketTagger.tagToKernel;
+
+import android.content.res.Resources;
+import android.net.NetworkStats;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.frameworks.servicestests.R;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@LargeTest
+public class NetworkManagementServiceTest extends AndroidTestCase {
+ private File mTestProc;
+ private NetworkManagementService mService;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mTestProc = getContext().getFilesDir();
+ mService = NetworkManagementService.createForTest(mContext, mTestProc);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mService = null;
+
+ super.tearDown();
+ }
+
+ public void testNetworkStatsDetail() throws Exception {
+ stageFile(R.raw.xt_qtaguid_typical, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+ final NetworkStats stats = mService.getNetworkStatsDetail();
+ assertEquals(31, stats.size);
+ assertStatsEntry(stats, "wlan0", 0, 0, 14615L, 4270L);
+ assertStatsEntry(stats, "wlan0", 10004, 0, 333821L, 53558L);
+ assertStatsEntry(stats, "wlan0", 10004, 1947740890, 18725L, 1066L);
+ assertStatsEntry(stats, "rmnet0", 10037, 0, 31184994L, 684122L);
+ assertStatsEntry(stats, "rmnet0", 10037, 1947740890, 28507378L, 437004L);
+ }
+
+ public void testNetworkStatsDetailExtended() throws Exception {
+ stageFile(R.raw.xt_qtaguid_extended, new File(mTestProc, "net/xt_qtaguid/stats"));
+
+ final NetworkStats stats = mService.getNetworkStatsDetail();
+ assertEquals(2, stats.size);
+ assertStatsEntry(stats, "test0", 1000, 0, 1024L, 2048L);
+ assertStatsEntry(stats, "test0", 1000, 0xF00D, 512L, 512L);
+ }
+
+ public void testKernelTags() throws Exception {
+ assertEquals("0", tagToKernel(0x0));
+ assertEquals("214748364800", tagToKernel(0x32));
+ assertEquals("9223372032559808512", tagToKernel(Integer.MAX_VALUE));
+ assertEquals("0", tagToKernel(Integer.MIN_VALUE));
+ assertEquals("9223369837831520256", tagToKernel(Integer.MIN_VALUE - 512));
+
+ assertEquals(0, kernelToTag("0x0000000000000000"));
+ assertEquals(0x32, kernelToTag("0x0000003200000000"));
+ assertEquals(2147483647, kernelToTag("0x7fffffff00000000"));
+ assertEquals(0, kernelToTag("0x0000000000000000"));
+ assertEquals(2147483136, kernelToTag("0x7FFFFE0000000000"));
+
+ }
+
+ /**
+ * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+ * testing purposes.
+ */
+ private void stageFile(int rawId, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private static void assertStatsEntry(
+ NetworkStats stats, String iface, int uid, int tag, long rx, long tx) {
+ final int i = stats.findIndex(iface, uid, tag);
+ assertEquals(rx, stats.rx[i]);
+ assertEquals(tx, stats.tx[i]);
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 07e5425..324e896 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -41,7 +41,9 @@
import android.app.INotificationManager;
import android.app.IProcessObserver;
import android.content.Intent;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.INetworkPolicyListener;
@@ -63,12 +65,17 @@
import android.util.TrustedTime;
import com.android.server.net.NetworkPolicyManagerService;
+import com.google.common.util.concurrent.AbstractFuture;
import org.easymock.Capture;
import org.easymock.EasyMock;
+import org.easymock.IAnswer;
import java.io.File;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Tests for {@link NetworkPolicyManagerService}.
@@ -118,11 +125,27 @@
public String[] getPackagesForUid(int uid) {
return new String[] { "com.example" };
}
+
+ @Override
+ public PackageInfo getPackageInfo(String packageName, int flags) {
+ final PackageInfo info = new PackageInfo();
+ final Signature signature;
+ if ("android".equals(packageName)) {
+ signature = new Signature("F00D");
+ } else {
+ signature = new Signature("DEAD");
+ }
+ info.signatures = new Signature[] { signature };
+ return info;
+ }
};
}
};
mPolicyDir = getContext().getFilesDir();
+ for (File file : mPolicyDir.listFiles()) {
+ file.delete();
+ }
mActivityManager = createMock(IActivityManager.class);
mPowerManager = createMock(IPowerManager.class);
@@ -228,81 +251,110 @@
}
public void testScreenChangesRules() throws Exception {
- // push strict policy for foreground uid, verify ALLOW rule
- expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ Future<Void> future;
+
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
replay();
mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ future.get();
+ verifyAndReset();
+
+ // push strict policy for foreground uid, verify ALLOW rule
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+ future.get();
verifyAndReset();
// now turn screen off and verify REJECT rule
expect(mPowerManager.isScreenOn()).andReturn(false).atLeastOnce();
- expectRulesChanged(UID_A, RULE_REJECT_METERED);
+ future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
replay();
mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF));
+ future.get();
verifyAndReset();
// and turn screen back on, verify ALLOW rule restored
expect(mPowerManager.isScreenOn()).andReturn(true).atLeastOnce();
- expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
replay();
mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_ON));
+ future.get();
verifyAndReset();
}
public void testPolicyNone() throws Exception {
+ Future<Void> future;
+
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ replay();
+ mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ future.get();
+ verifyAndReset();
+
// POLICY_NONE should RULE_ALLOW in foreground
- expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
replay();
mService.setUidPolicy(UID_A, POLICY_NONE);
- mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ future.get();
verifyAndReset();
// POLICY_NONE should RULE_ALLOW in background
- expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
replay();
mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ future.get();
verifyAndReset();
}
public void testPolicyReject() throws Exception {
+ Future<Void> future;
+
// POLICY_REJECT should RULE_ALLOW in background
- expectRulesChanged(UID_A, RULE_REJECT_METERED);
+ future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
replay();
mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+ future.get();
verifyAndReset();
// POLICY_REJECT should RULE_ALLOW in foreground
- expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
replay();
mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true);
+ future.get();
verifyAndReset();
// POLICY_REJECT should RULE_REJECT in background
- expectRulesChanged(UID_A, RULE_REJECT_METERED);
+ future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
replay();
mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ future.get();
verifyAndReset();
}
public void testPolicyRejectAddRemove() throws Exception {
+ Future<Void> future;
+
// POLICY_NONE should have RULE_ALLOW in background
- expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
replay();
- mService.setUidPolicy(UID_A, POLICY_NONE);
mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false);
+ mService.setUidPolicy(UID_A, POLICY_NONE);
+ future.get();
verifyAndReset();
// adding POLICY_REJECT should cause RULE_REJECT
- expectRulesChanged(UID_A, RULE_REJECT_METERED);
+ future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
replay();
mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+ future.get();
verifyAndReset();
// removing POLICY_REJECT should return us to RULE_ALLOW
- expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
replay();
mService.setUidPolicy(UID_A, POLICY_NONE);
+ future.get();
verifyAndReset();
}
@@ -350,6 +402,7 @@
long elapsedRealtime = 0;
NetworkState[] state = null;
NetworkStats stats = null;
+ Future<Void> future;
final long TIME_FEB_15 = 1171497600000L;
final long TIME_MAR_10 = 1173484800000L;
@@ -360,10 +413,11 @@
state = new NetworkState[] { buildWifi() };
expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
expectTime(TIME_MAR_10 + elapsedRealtime);
- expectMeteredIfacesChanged();
+ future = expectMeteredIfacesChanged();
replay();
mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
+ future.get();
verifyAndReset();
// now change cycle to be on 15th, and test in early march, to verify we
@@ -381,26 +435,31 @@
// TODO: write up NetworkManagementService mock
expectClearNotifications();
- expectMeteredIfacesChanged(TEST_IFACE);
+ future = expectMeteredIfacesChanged(TEST_IFACE);
replay();
setNetworkPolicies(new NetworkPolicy(sTemplateWifi, CYCLE_DAY, 1024L, 2048L));
+ future.get();
verifyAndReset();
}
public void testUidRemovedPolicyCleared() throws Exception {
+ Future<Void> future;
+
// POLICY_REJECT should RULE_REJECT in background
- expectRulesChanged(UID_A, RULE_REJECT_METERED);
+ future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
replay();
mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND);
+ future.get();
verifyAndReset();
// uninstall should clear RULE_REJECT
- expectRulesChanged(UID_A, RULE_ALLOW_ALL);
+ future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
replay();
final Intent intent = new Intent(ACTION_UID_REMOVED);
intent.putExtra(EXTRA_UID, UID_A);
mServiceContext.sendBroadcast(intent);
+ future.get();
verifyAndReset();
}
@@ -435,14 +494,35 @@
expectLastCall().anyTimes();
}
- private void expectRulesChanged(int uid, int policy) throws Exception {
+ private Future<Void> expectRulesChanged(int uid, int policy) throws Exception {
+ final FutureAnswer future = new FutureAnswer();
mPolicyListener.onUidRulesChanged(eq(uid), eq(policy));
- expectLastCall().atLeastOnce();
+ expectLastCall().andAnswer(future);
+ return future;
}
- private void expectMeteredIfacesChanged(String... ifaces) throws Exception {
+ private Future<Void> expectMeteredIfacesChanged(String... ifaces) throws Exception {
+ final FutureAnswer future = new FutureAnswer();
mPolicyListener.onMeteredIfacesChanged(aryEq(ifaces));
- expectLastCall().atLeastOnce();
+ expectLastCall().andAnswer(future);
+ return future;
+ }
+
+ private static class FutureAnswer extends AbstractFuture<Void> implements IAnswer<Void> {
+ @Override
+ public Void get() throws InterruptedException, ExecutionException {
+ try {
+ return get(5, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public Void answer() {
+ set(null);
+ return null;
+ }
}
private void replay() {
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 636d059..903f2b0 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -59,7 +59,6 @@
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
import android.util.TrustedTime;
import com.android.server.net.NetworkStatsService;
@@ -611,6 +610,9 @@
mAlarmManager.setInexactRepeating(
eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), isA(PendingIntent.class));
expectLastCall().atLeastOnce();
+
+ mNetManager.setBandwidthControlEnabled(true);
+ expectLastCall().atLeastOnce();
}
private void expectNetworkState(NetworkState... state) throws Exception {
@@ -631,6 +633,7 @@
private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory)
throws Exception {
+ expect(mSettings.getEnabled()).andReturn(true).anyTimes();
expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes();
expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes();
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index c4a6f53..6e9d0f9d 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -502,16 +502,17 @@
@SmallTest
public void testFormatNumberToE164() {
- assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "us"));
- assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "us"));
- assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "us"));
+ // Note: ISO 3166-1 only allows upper case country codes.
+ assertEquals("+16502910000", PhoneNumberUtils.formatNumberToE164("650 2910000", "US"));
+ assertNull(PhoneNumberUtils.formatNumberToE164("1234567", "US"));
+ assertEquals("+18004664114", PhoneNumberUtils.formatNumberToE164("800-GOOG-114", "US"));
}
@SmallTest
public void testFormatNumber() {
- assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "us"));
- assertEquals("123-4567", PhoneNumberUtils.formatNumber("1234567", "us"));
- assertEquals("(800) 466-4114", PhoneNumberUtils.formatNumber("800-GOOG-114", "us"));
+ assertEquals("(650) 291-0000", PhoneNumberUtils.formatNumber("650 2910000", "US"));
+ assertEquals("123-4567", PhoneNumberUtils.formatNumber("1234567", "US"));
+ assertEquals("(800) 466-4114", PhoneNumberUtils.formatNumber("800-GOOG-114", "US"));
}
diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java
index 47950e3..c031bc1 100644
--- a/voip/java/com/android/server/sip/SipHelper.java
+++ b/voip/java/com/android/server/sip/SipHelper.java
@@ -150,9 +150,17 @@
private ContactHeader createContactHeader(SipProfile profile)
throws ParseException, SipException {
- ListeningPoint lp = getListeningPoint();
- SipURI contactURI =
- createSipUri(profile.getUserName(), profile.getProtocol(), lp);
+ return createContactHeader(profile, null, 0);
+ }
+
+ private ContactHeader createContactHeader(SipProfile profile,
+ String ip, int port) throws ParseException,
+ SipException {
+ SipURI contactURI = (ip == null)
+ ? createSipUri(profile.getUserName(), profile.getProtocol(),
+ getListeningPoint())
+ : createSipUri(profile.getUserName(), profile.getProtocol(),
+ ip, port);
Address contactAddress = mAddressFactory.createAddress(contactURI);
contactAddress.setDisplayName(profile.getDisplayName());
@@ -168,9 +176,14 @@
private SipURI createSipUri(String username, String transport,
ListeningPoint lp) throws ParseException {
- SipURI uri = mAddressFactory.createSipURI(username, lp.getIPAddress());
+ return createSipUri(username, transport, lp.getIPAddress(), lp.getPort());
+ }
+
+ private SipURI createSipUri(String username, String transport,
+ String ip, int port) throws ParseException {
+ SipURI uri = mAddressFactory.createSipURI(username, ip);
try {
- uri.setPort(lp.getPort());
+ uri.setPort(port);
uri.setTransportParam(transport);
} catch (InvalidArgumentException e) {
throw new RuntimeException(e);
@@ -353,13 +366,14 @@
*/
public ServerTransaction sendInviteOk(RequestEvent event,
SipProfile localProfile, String sessionDescription,
- ServerTransaction inviteTransaction)
- throws SipException {
+ ServerTransaction inviteTransaction, String externalIp,
+ int externalPort) throws SipException {
try {
Request request = event.getRequest();
Response response = mMessageFactory.createResponse(Response.OK,
request);
- response.addHeader(createContactHeader(localProfile));
+ response.addHeader(createContactHeader(localProfile, externalIp,
+ externalPort));
response.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index c0c1b28..047eb8d 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -119,6 +119,10 @@
private Map<String, SipSessionImpl> mSessionMap =
new HashMap<String, SipSessionImpl>();
+ // external address observed from any response
+ private String mExternalIp;
+ private int mExternalPort;
+
/**
* @param myself the local profile with password crossed out
* @param password the password of the profile
@@ -175,6 +179,8 @@
mCallReceiverSession = null;
mSessionMap.clear();
+
+ resetExternalAddress();
}
synchronized void onConnectivityChanged() {
@@ -190,6 +196,12 @@
}
}
+ synchronized void resetExternalAddress() {
+ Log.d(TAG, " reset external addr on " + mSipStack);
+ mExternalIp = null;
+ mExternalPort = 0;
+ }
+
public SipProfile getLocalProfile() {
return mLocalProfile;
}
@@ -363,6 +375,21 @@
return null;
}
+ private void extractExternalAddress(ResponseEvent evt) {
+ Response response = evt.getResponse();
+ ViaHeader viaHeader = (ViaHeader)(response.getHeader(
+ SIPHeaderNames.VIA));
+ if (viaHeader == null) return;
+ int rport = viaHeader.getRPort();
+ String externalIp = viaHeader.getReceived();
+ if ((rport > 0) && (externalIp != null)) {
+ mExternalIp = externalIp;
+ mExternalPort = rport;
+ Log.d(TAG, " got external addr " + externalIp + ":" + rport
+ + " on " + mSipStack);
+ }
+ }
+
private class SipSessionCallReceiverImpl extends SipSessionImpl {
public SipSessionCallReceiverImpl(ISipSessionListener listener) {
super(listener);
@@ -682,6 +709,7 @@
dialog = ((RequestEvent) evt).getDialog();
} else if (evt instanceof ResponseEvent) {
dialog = ((ResponseEvent) evt).getDialog();
+ extractExternalAddress((ResponseEvent) evt);
}
if (dialog != null) mDialog = dialog;
@@ -984,7 +1012,8 @@
mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived,
mLocalProfile,
((MakeCallCommand) evt).getSessionDescription(),
- mServerTransaction);
+ mServerTransaction,
+ mExternalIp, mExternalPort);
startSessionTimer(((MakeCallCommand) evt).getTimeout());
return true;
} else if (END_CALL == evt) {
@@ -1376,6 +1405,7 @@
if (evt instanceof ResponseEvent) {
if (parseOptionsResult(evt)) {
if (mPortChanged) {
+ resetExternalAddress();
stop();
} else {
cancelSessionTimer();
@@ -1405,8 +1435,11 @@
if (!mRunning) return;
if (DEBUG_PING) {
+ String peerUri = (mPeerProfile == null)
+ ? "null"
+ : mPeerProfile.getUriString();
Log.d(TAG, "keepalive: " + mLocalProfile.getUriString()
- + " --> " + mPeerProfile + ", interval=" + mInterval);
+ + " --> " + peerUri + ", interval=" + mInterval);
}
try {
sendKeepAlive();