summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountManagerService.java3
-rw-r--r--core/java/android/app/ActivityManager.java26
-rw-r--r--core/java/android/app/ActivityThread.java17
-rw-r--r--core/java/android/content/ContentResolver.java14
-rw-r--r--core/java/android/content/ContentService.java25
-rw-r--r--core/java/android/content/Intent.java174
-rw-r--r--core/java/android/database/AbstractCursor.java19
-rw-r--r--core/java/android/database/ContentObservable.java55
-rw-r--r--core/java/android/database/ContentObserver.java191
-rw-r--r--core/java/android/database/Cursor.java1
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java5
-rw-r--r--core/java/android/database/DataSetObservable.java15
-rwxr-xr-xcore/java/android/database/IContentObserver.aidl4
-rw-r--r--core/java/android/database/Observable.java13
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java210
-rw-r--r--core/java/android/database/sqlite/SQLiteConnectionPool.java9
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java8
-rw-r--r--core/java/android/database/sqlite/SQLiteDebug.java106
-rw-r--r--core/java/android/database/sqlite/SQLiteGlobal.java42
-rw-r--r--core/java/android/database/sqlite/SQLiteOpenHelper.java7
-rw-r--r--core/java/android/database/sqlite/SQLiteSession.java62
-rw-r--r--core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java31
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java3
-rw-r--r--core/java/android/net/NetworkStats.java39
-rw-r--r--core/java/android/net/NetworkStatsHistory.java78
-rw-r--r--core/java/android/net/NetworkTemplate.java10
-rw-r--r--core/java/android/net/TrafficStats.java2
-rw-r--r--core/java/android/net/Uri.java33
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl3
-rw-r--r--core/java/android/nfc/LlcpPacket.aidl22
-rw-r--r--core/java/android/nfc/LlcpPacket.java85
-rw-r--r--core/java/android/nfc/NdefMessage.java16
-rw-r--r--core/java/android/nfc/NdefRecord.java307
-rw-r--r--core/java/android/nfc/NfcAdapter.java36
-rw-r--r--core/java/android/os/Build.java65
-rw-r--r--core/java/android/os/IPowerManager.aidl1
-rw-r--r--core/java/android/provider/Settings.java38
-rw-r--r--core/java/android/provider/UserDictionary.java75
-rw-r--r--core/java/android/text/MeasuredText.java3
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java2
-rw-r--r--core/java/android/util/DisplayMetrics.java7
-rw-r--r--core/java/android/util/LocalLog.java56
-rw-r--r--core/java/android/view/GLES20Canvas.java44
-rw-r--r--core/java/android/view/HardwareRenderer.java12
-rw-r--r--core/java/android/view/IWindowManager.aidl5
-rw-r--r--core/java/android/view/View.java109
-rw-r--r--core/java/android/view/ViewTreeObserver.java19
-rw-r--r--core/java/android/view/WindowManagerPolicy.java5
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java386
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java16
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java16
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java4
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java44
-rw-r--r--core/java/android/webkit/HTML5VideoFullScreen.java4
-rw-r--r--core/java/android/webkit/HTML5VideoView.java44
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java15
-rw-r--r--core/java/android/webkit/SelectActionModeCallback.java53
-rw-r--r--core/java/android/webkit/WebView.java284
-rw-r--r--core/java/android/webkit/WebViewCore.java85
-rw-r--r--core/java/android/widget/ActivityChooserView.java8
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java4
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java5
-rw-r--r--core/java/android/widget/NumberPicker.java24
-rw-r--r--core/java/android/widget/ShareActionProvider.java1
-rw-r--r--core/java/android/widget/SpellChecker.java3
-rw-r--r--core/java/android/widget/TextView.java144
-rw-r--r--core/java/com/android/internal/backup/BackupConstants.java1
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java2
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java8
-rw-r--r--core/java/com/android/internal/util/FileRotator.java121
-rw-r--r--core/java/com/android/internal/util/IndentingPrintWriter.java63
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java4
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java54
74 files changed, 2299 insertions, 1213 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 4f3405b..5fee4de 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -62,6 +62,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -275,7 +276,7 @@ public class AccountManagerService
accountNames.add(accountName);
}
}
- for (HashMap.Entry<String, ArrayList<String>> cur
+ for (Map.Entry<String, ArrayList<String>> cur
: accountNamesByType.entrySet()) {
final String accountType = cur.getKey();
final ArrayList<String> accountNames = cur.getValue();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4fe9cef..9661b9e 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1442,9 +1442,10 @@ public class ActivityManager {
public int getLauncherLargeIconDensity() {
final Resources res = mContext.getResources();
final int density = res.getDisplayMetrics().densityDpi;
+ final int sw = res.getConfiguration().smallestScreenWidthDp;
- if ((res.getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
- != Configuration.SCREENLAYOUT_SIZE_XLARGE) {
+ if (sw < 600) {
+ // Smaller than approx 7" tablets, use the regular icon size.
return density;
}
@@ -1456,9 +1457,13 @@ public class ActivityManager {
case DisplayMetrics.DENSITY_HIGH:
return DisplayMetrics.DENSITY_XHIGH;
case DisplayMetrics.DENSITY_XHIGH:
- return DisplayMetrics.DENSITY_MEDIUM * 2;
+ return DisplayMetrics.DENSITY_XXHIGH;
+ case DisplayMetrics.DENSITY_XXHIGH:
+ return DisplayMetrics.DENSITY_XHIGH * 2;
default:
- return density;
+ // The density is some abnormal value. Return some other
+ // abnormal value that is a reasonable scaling of it.
+ return (int)(density*1.5f);
}
}
@@ -1471,9 +1476,10 @@ public class ActivityManager {
public int getLauncherLargeIconSize() {
final Resources res = mContext.getResources();
final int size = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
+ final int sw = res.getConfiguration().smallestScreenWidthDp;
- if ((res.getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK)
- != Configuration.SCREENLAYOUT_SIZE_XLARGE) {
+ if (sw < 600) {
+ // Smaller than approx 7" tablets, use the regular icon size.
return size;
}
@@ -1487,9 +1493,13 @@ public class ActivityManager {
case DisplayMetrics.DENSITY_HIGH:
return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH;
case DisplayMetrics.DENSITY_XHIGH:
- return (size * DisplayMetrics.DENSITY_MEDIUM * 2) / DisplayMetrics.DENSITY_XHIGH;
+ return (size * DisplayMetrics.DENSITY_XXHIGH) / DisplayMetrics.DENSITY_XHIGH;
+ case DisplayMetrics.DENSITY_XXHIGH:
+ return (size * DisplayMetrics.DENSITY_XHIGH*2) / DisplayMetrics.DENSITY_XXHIGH;
default:
- return size;
+ // The density is some abnormal value. Return some other
+ // abnormal value that is a reasonable scaling of it.
+ return (int)(size*1.5f);
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bac3c6c..9807b89 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -486,7 +486,6 @@ public final class ActivityThread {
private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%21s %8d";
private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";
- private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d";
private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
// Formatting for checkin service - update version if row format changes
@@ -867,7 +866,6 @@ public final class ActivityThread {
int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
long openSslSocketCount = Debug.countInstancesOfClass(OpenSSLSocketImpl.class);
- long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
// For checkin, we print one long comma-separated list of values
@@ -935,9 +933,9 @@ public final class ActivityThread {
pw.print(openSslSocketCount); pw.print(',');
// SQL
- pw.print(sqliteAllocated); pw.print(',');
pw.print(stats.memoryUsed / 1024); pw.print(',');
- pw.print(stats.pageCacheOverflo / 1024); pw.print(',');
+ pw.print(stats.memoryUsed / 1024); pw.print(',');
+ pw.print(stats.pageCacheOverflow / 1024); pw.print(',');
pw.print(stats.largestMemAlloc / 1024);
for (int i = 0; i < stats.dbStats.size(); i++) {
DbStats dbStats = stats.dbStats.get(i);
@@ -1003,10 +1001,9 @@ public final class ActivityThread {
// SQLite mem info
pw.println(" ");
pw.println(" SQL");
- printRow(pw, TWO_COUNT_COLUMNS_DB, "heap:", sqliteAllocated, "MEMORY_USED:",
- stats.memoryUsed / 1024);
- printRow(pw, TWO_COUNT_COLUMNS_DB, "PAGECACHE_OVERFLOW:",
- stats.pageCacheOverflo / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024);
+ printRow(pw, ONE_COUNT_COLUMN, "MEMORY_USED:", stats.memoryUsed / 1024);
+ printRow(pw, TWO_COUNT_COLUMNS, "PAGECACHE_OVERFLOW:",
+ stats.pageCacheOverflow / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024);
pw.println(" ");
int N = stats.dbStats.size();
if (N > 0) {
@@ -3744,7 +3741,6 @@ public final class ActivityThread {
}
final void handleTrimMemory(int level) {
- WindowManagerImpl.getDefault().trimMemory(level);
ArrayList<ComponentCallbacks2> callbacks;
synchronized (mPackages) {
@@ -3755,6 +3751,7 @@ public final class ActivityThread {
for (int i=0; i<N; i++) {
callbacks.get(i).onTrimMemory(level);
}
+ WindowManagerImpl.getDefault().trimMemory(level);
}
private void setupGraphicsSupport(LoadedApk info) {
@@ -3807,7 +3804,7 @@ public final class ActivityThread {
// implementation to use the pool executor. Normally, we use the
// serialized executor as the default. This has to happen in the
// main thread so the main looper is set right.
- if (data.appInfo.targetSdkVersion <= 12) {
+ if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index cc3219b..0debb84 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -1034,8 +1034,11 @@ public abstract class ContentResolver {
* To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
* By default, CursorAdapter objects will get this notification.
*
- * @param uri
- * @param observer The observer that originated the change, may be <code>null</null>
+ * @param uri The uri of the content that was changed.
+ * @param observer The observer that originated the change, may be <code>null</null>.
+ * The observer that originated the change will only receive the notification if it
+ * has requested to receive self-change notifications by implementing
+ * {@link ContentObserver#deliverSelfNotifications()} to return true.
*/
public void notifyChange(Uri uri, ContentObserver observer) {
notifyChange(uri, observer, true /* sync to network */);
@@ -1046,8 +1049,11 @@ public abstract class ContentResolver {
* To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
* By default, CursorAdapter objects will get this notification.
*
- * @param uri
- * @param observer The observer that originated the change, may be <code>null</null>
+ * @param uri The uri of the content that was changed.
+ * @param observer The observer that originated the change, may be <code>null</null>.
+ * The observer that originated the change will only receive the notification if it
+ * has requested to receive self-change notifications by implementing
+ * {@link ContentObserver#deliverSelfNotifications()} to return true.
* @param syncToNetwork If true, attempt to sync the change to the network.
*/
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index 0e83dc0..fc4c262 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -176,7 +176,7 @@ public final class ContentService extends IContentService.Stub {
for (int i=0; i<numCalls; i++) {
ObserverCall oc = calls.get(i);
try {
- oc.mObserver.onChange(oc.mSelfNotify);
+ oc.mObserver.onChange(oc.mSelfChange, uri);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
}
@@ -218,13 +218,12 @@ public final class ContentService extends IContentService.Stub {
public static final class ObserverCall {
final ObserverNode mNode;
final IContentObserver mObserver;
- final boolean mSelfNotify;
+ final boolean mSelfChange;
- ObserverCall(ObserverNode node, IContentObserver observer,
- boolean selfNotify) {
+ ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) {
mNode = node;
mObserver = observer;
- mSelfNotify = selfNotify;
+ mSelfChange = selfChange;
}
}
@@ -668,7 +667,7 @@ public final class ContentService extends IContentService.Stub {
}
private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
- boolean selfNotify, ArrayList<ObserverCall> calls) {
+ boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
@@ -676,28 +675,29 @@ public final class ContentService extends IContentService.Stub {
// Don't notify the observer if it sent the notification and isn't interesed
// in self notifications
- if (entry.observer.asBinder() == observerBinder && !selfNotify) {
+ boolean selfChange = (entry.observer.asBinder() == observerBinder);
+ if (selfChange && !observerWantsSelfNotifications) {
continue;
}
// Make sure the observer is interested in the notification
if (leaf || (!leaf && entry.notifyForDescendents)) {
- calls.add(new ObserverCall(this, entry.observer, selfNotify));
+ calls.add(new ObserverCall(this, entry.observer, selfChange));
}
}
}
public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
- boolean selfNotify, ArrayList<ObserverCall> calls) {
+ boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
- collectMyObserversLocked(true, observer, selfNotify, calls);
+ collectMyObserversLocked(true, observer, observerWantsSelfNotifications, calls);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
// Notify any observers at this level who are interested in descendents
- collectMyObserversLocked(false, observer, selfNotify, calls);
+ collectMyObserversLocked(false, observer, observerWantsSelfNotifications, calls);
}
int N = mChildren.size();
@@ -705,7 +705,8 @@ public final class ContentService extends IContentService.Stub {
ObserverNode node = mChildren.get(i);
if (segment == null || node.mName.equals(segment)) {
// We found the child,
- node.collectObserversLocked(uri, index + 1, observer, selfNotify, calls);
+ node.collectObserversLocked(uri, index + 1,
+ observer, observerWantsSelfNotifications, calls);
if (segment != null) {
break;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e3b1f54..fbc1b2b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -43,6 +43,7 @@ import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Locale;
import java.util.Set;
/**
@@ -4420,22 +4421,24 @@ public class Intent implements Parcelable, Cloneable {
/**
* Set the data this intent is operating on. This method automatically
- * clears any type that was previously set by {@link #setType}.
+ * clears any type that was previously set by {@link #setType} or
+ * {@link #setTypeAndNormalize}.
*
- * <p><em>Note: scheme and host name matching in the Android framework is
- * case-sensitive, unlike the formal RFC. As a result,
- * you should always ensure that you write your Uri with these elements
- * using lower case letters, and normalize any Uris you receive from
- * outside of Android to ensure the scheme and host is lower case.</em></p>
+ * <p><em>Note: scheme matching in the Android framework is
+ * case-sensitive, unlike the formal RFC. As a result,
+ * you should always write your Uri with a lower case scheme,
+ * or use {@link Uri#normalize} or
+ * {@link #setDataAndNormalize}
+ * to ensure that the scheme is converted to lower case.</em>
*
- * @param data The URI of the data this intent is now targeting.
+ * @param data The Uri of the data this intent is now targeting.
*
* @return Returns the same Intent object, for chaining multiple calls
* into a single statement.
*
* @see #getData
- * @see #setType
- * @see #setDataAndType
+ * @see #setDataAndNormalize
+ * @see android.net.Intent#normalize
*/
public Intent setData(Uri data) {
mData = data;
@@ -4444,16 +4447,45 @@ public class Intent implements Parcelable, Cloneable {
}
/**
- * Set an explicit MIME data type. This is used to create intents that
- * only specify a type and not data, for example to indicate the type of
- * data to return. This method automatically clears any data that was
- * previously set by {@link #setData}.
+ * Normalize and set the data this intent is operating on.
+ *
+ * <p>This method automatically clears any type that was
+ * previously set (for example, by {@link #setType}).
+ *
+ * <p>The data Uri is normalized using
+ * {@link android.net.Uri#normalize} before it is set,
+ * so really this is just a convenience method for
+ * <pre>
+ * setData(data.normalize())
+ * </pre>
+ *
+ * @param data The Uri of the data this intent is now targeting.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getData
+ * @see #setType
+ * @see android.net.Uri#normalize
+ */
+ public Intent setDataAndNormalize(Uri data) {
+ return setData(data.normalize());
+ }
+
+ /**
+ * Set an explicit MIME data type.
+ *
+ * <p>This is used to create intents that only specify a type and not data,
+ * for example to indicate the type of data to return.
+ *
+ * <p>This method automatically clears any data that was
+ * previously set (for example by {@link #setData}).
*
* <p><em>Note: MIME type matching in the Android framework is
* case-sensitive, unlike formal RFC MIME types. As a result,
* you should always write your MIME types with lower case letters,
- * and any MIME types you receive from outside of Android should be
- * converted to lower case before supplying them here.</em></p>
+ * or use {@link #normalizeMimeType} or {@link #setTypeAndNormalize}
+ * to ensure that it is converted to lower case.</em>
*
* @param type The MIME type of the data being handled by this intent.
*
@@ -4461,8 +4493,9 @@ public class Intent implements Parcelable, Cloneable {
* into a single statement.
*
* @see #getType
- * @see #setData
+ * @see #setTypeAndNormalize
* @see #setDataAndType
+ * @see #normalizeMimeType
*/
public Intent setType(String type) {
mData = null;
@@ -4471,26 +4504,58 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Normalize and set an explicit MIME data type.
+ *
+ * <p>This is used to create intents that only specify a type and not data,
+ * for example to indicate the type of data to return.
+ *
+ * <p>This method automatically clears any data that was
+ * previously set (for example by {@link #setData}).
+ *
+ * <p>The MIME type is normalized using
+ * {@link #normalizeMimeType} before it is set,
+ * so really this is just a convenience method for
+ * <pre>
+ * setType(Intent.normalizeMimeType(type))
+ * </pre>
+ *
+ * @param type The MIME type of the data being handled by this intent.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getType
+ * @see #setData
+ * @see #normalizeMimeType
+ */
+ public Intent setTypeAndNormalize(String type) {
+ return setType(normalizeMimeType(type));
+ }
+
+ /**
* (Usually optional) Set the data for the intent along with an explicit
* MIME data type. This method should very rarely be used -- it allows you
* to override the MIME type that would ordinarily be inferred from the
* data with your own type given here.
*
- * <p><em>Note: MIME type, Uri scheme, and host name matching in the
+ * <p><em>Note: MIME type and Uri scheme matching in the
* Android framework is case-sensitive, unlike the formal RFC definitions.
* As a result, you should always write these elements with lower case letters,
- * and normalize any MIME types or Uris you receive from
- * outside of Android to ensure these elements are lower case before
- * supplying them here.</em></p>
+ * or use {@link #normalizeMimeType} or {@link android.net.Uri#normalize} or
+ * {@link #setDataAndTypeAndNormalize}
+ * to ensure that they are converted to lower case.</em>
*
- * @param data The URI of the data this intent is now targeting.
+ * @param data The Uri of the data this intent is now targeting.
* @param type The MIME type of the data being handled by this intent.
*
* @return Returns the same Intent object, for chaining multiple calls
* into a single statement.
*
- * @see #setData
* @see #setType
+ * @see #setData
+ * @see #normalizeMimeType
+ * @see android.net.Uri#normalize
+ * @see #setDataAndTypeAndNormalize
*/
public Intent setDataAndType(Uri data, String type) {
mData = data;
@@ -4499,6 +4564,35 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * (Usually optional) Normalize and set both the data Uri and an explicit
+ * MIME data type. This method should very rarely be used -- it allows you
+ * to override the MIME type that would ordinarily be inferred from the
+ * data with your own type given here.
+ *
+ * <p>The data Uri and the MIME type are normalize using
+ * {@link android.net.Uri#normalize} and {@link #normalizeMimeType}
+ * before they are set, so really this is just a convenience method for
+ * <pre>
+ * setDataAndType(data.normalize(), Intent.normalizeMimeType(type))
+ * </pre>
+ *
+ * @param data The Uri of the data this intent is now targeting.
+ * @param type The MIME type of the data being handled by this intent.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #setType
+ * @see #setData
+ * @see #setDataAndType
+ * @see #normalizeMimeType
+ * @see android.net.Uri#normalize
+ */
+ public Intent setDataAndTypeAndNormalize(Uri data, String type) {
+ return setDataAndType(data.normalize(), normalizeMimeType(type));
+ }
+
+ /**
* Add a new category to the intent. Categories provide additional detail
* about the action the intent is perform. When resolving an intent, only
* activities that provide <em>all</em> of the requested categories will be
@@ -5566,7 +5660,7 @@ public class Intent implements Parcelable, Cloneable {
*
* <ul>
* <li> action, as set by {@link #setAction}.
- * <li> data URI and MIME type, as set by {@link #setData(Uri)},
+ * <li> data Uri and MIME type, as set by {@link #setData(Uri)},
* {@link #setType(String)}, or {@link #setDataAndType(Uri, String)}.
* <li> categories, as set by {@link #addCategory}.
* <li> package, as set by {@link #setPackage}.
@@ -6229,4 +6323,38 @@ public class Intent implements Parcelable, Cloneable {
return intent;
}
+
+ /**
+ * Normalize a MIME data type.
+ *
+ * <p>A normalized MIME type has white-space trimmed,
+ * content-type parameters removed, and is lower-case.
+ * This aligns the type with Android best practices for
+ * intent filtering.
+ *
+ * <p>For example, "text/plain; charset=utf-8" becomes "text/plain".
+ * "text/x-vCard" becomes "text/x-vcard".
+ *
+ * <p>All MIME types received from outside Android (such as user input,
+ * or external sources like Bluetooth, NFC, or the Internet) should
+ * be normalized before they are used to create an Intent.
+ *
+ * @param type MIME data type to normalize
+ * @return normalized MIME data type, or null if the input was null
+ * @see {@link #setType}
+ * @see {@link #setTypeAndNormalize}
+ */
+ public static String normalizeMimeType(String type) {
+ if (type == null) {
+ return null;
+ }
+
+ type = type.trim().toLowerCase(Locale.US);
+
+ final int semicolonIndex = type.indexOf(';');
+ if (semicolonIndex != -1) {
+ type = type.substring(0, semicolonIndex);
+ }
+ return type;
+ }
}
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 74fef29..b28ed8d 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -284,23 +284,6 @@ public abstract class AbstractCursor implements CrossProcessCursor {
}
}
- /**
- * This is hidden until the data set change model has been re-evaluated.
- * @hide
- */
- protected void notifyDataSetChange() {
- mDataSetObservable.notifyChanged();
- }
-
- /**
- * This is hidden until the data set change model has been re-evaluated.
- * @hide
- */
- protected DataSetObservable getDataSetObservable() {
- return mDataSetObservable;
-
- }
-
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
@@ -317,7 +300,7 @@ public abstract class AbstractCursor implements CrossProcessCursor {
*/
protected void onChange(boolean selfChange) {
synchronized (mSelfObserverLock) {
- mContentObservable.dispatchChange(selfChange);
+ mContentObservable.dispatchChange(selfChange, null);
if (mNotifyUri != null && selfChange) {
mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
}
diff --git a/core/java/android/database/ContentObservable.java b/core/java/android/database/ContentObservable.java
index 8d7b7c5..7692bb3 100644
--- a/core/java/android/database/ContentObservable.java
+++ b/core/java/android/database/ContentObservable.java
@@ -16,40 +16,75 @@
package android.database;
+import android.net.Uri;
+
/**
- * A specialization of Observable for ContentObserver that provides methods for
- * invoking the various callback methods of ContentObserver.
+ * A specialization of {@link Observable} for {@link ContentObserver}
+ * that provides methods for sending notifications to a list of
+ * {@link ContentObserver} objects.
*/
public class ContentObservable extends Observable<ContentObserver> {
-
+ // Even though the generic method defined in Observable would be perfectly
+ // fine on its own, we can't delete this overridden method because it would
+ // potentially break binary compatibility with existing applications.
@Override
public void registerObserver(ContentObserver observer) {
super.registerObserver(observer);
}
/**
- * invokes dispatchUpdate on each observer, unless the observer doesn't want
- * self-notifications and the update is from a self-notification
- * @param selfChange
+ * Invokes {@link ContentObserver#dispatchChange(boolean)} on each observer.
+ * <p>
+ * If <code>selfChange</code> is true, only delivers the notification
+ * to the observer if it has indicated that it wants to receive self-change
+ * notifications by implementing {@link ContentObserver#deliverSelfNotifications}
+ * to return true.
+ * </p>
+ *
+ * @param selfChange True if this is a self-change notification.
+ *
+ * @deprecated Use {@link #dispatchChange(boolean, Uri)} instead.
*/
+ @Deprecated
public void dispatchChange(boolean selfChange) {
+ dispatchChange(selfChange, null);
+ }
+
+ /**
+ * Invokes {@link ContentObserver#dispatchChange(boolean, Uri)} on each observer.
+ * Includes the changed content Uri when available.
+ * <p>
+ * If <code>selfChange</code> is true, only delivers the notification
+ * to the observer if it has indicated that it wants to receive self-change
+ * notifications by implementing {@link ContentObserver#deliverSelfNotifications}
+ * to return true.
+ * </p>
+ *
+ * @param selfChange True if this is a self-change notification.
+ * @param uri The Uri of the changed content, or null if unknown.
+ */
+ public void dispatchChange(boolean selfChange, Uri uri) {
synchronized(mObservers) {
for (ContentObserver observer : mObservers) {
if (!selfChange || observer.deliverSelfNotifications()) {
- observer.dispatchChange(selfChange);
+ observer.dispatchChange(selfChange, uri);
}
}
}
}
/**
- * invokes onChange on each observer
- * @param selfChange
+ * Invokes {@link ContentObserver#onChange} on each observer.
+ *
+ * @param selfChange True if this is a self-change notification.
+ *
+ * @deprecated Use {@link #dispatchChange} instead.
*/
+ @Deprecated
public void notifyChange(boolean selfChange) {
synchronized(mObservers) {
for (ContentObserver observer : mObservers) {
- observer.onChange(selfChange);
+ observer.onChange(selfChange, null);
}
}
}
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
index 3b829a3..e4fbc28 100644
--- a/core/java/android/database/ContentObserver.java
+++ b/core/java/android/database/ContentObserver.java
@@ -16,65 +16,23 @@
package android.database;
+import android.net.Uri;
import android.os.Handler;
/**
- * Receives call backs for changes to content. Must be implemented by objects which are added
- * to a {@link ContentObservable}.
+ * Receives call backs for changes to content.
+ * Must be implemented by objects which are added to a {@link ContentObservable}.
*/
public abstract class ContentObserver {
+ private final Object mLock = new Object();
+ private Transport mTransport; // guarded by mLock
- private Transport mTransport;
-
- // Protects mTransport
- private Object lock = new Object();
-
- /* package */ Handler mHandler;
-
- private final class NotificationRunnable implements Runnable {
-
- private boolean mSelf;
-
- public NotificationRunnable(boolean self) {
- mSelf = self;
- }
-
- public void run() {
- ContentObserver.this.onChange(mSelf);
- }
- }
-
- private static final class Transport extends IContentObserver.Stub {
- ContentObserver mContentObserver;
-
- public Transport(ContentObserver contentObserver) {
- mContentObserver = contentObserver;
- }
-
- public boolean deliverSelfNotifications() {
- ContentObserver contentObserver = mContentObserver;
- if (contentObserver != null) {
- return contentObserver.deliverSelfNotifications();
- }
- return false;
- }
-
- public void onChange(boolean selfChange) {
- ContentObserver contentObserver = mContentObserver;
- if (contentObserver != null) {
- contentObserver.dispatchChange(selfChange);
- }
- }
-
- public void releaseContentObserver() {
- mContentObserver = null;
- }
- }
+ Handler mHandler;
/**
- * onChange() will happen on the provider Handler.
+ * Creates a content observer.
*
- * @param handler The handler to run {@link #onChange} on.
+ * @param handler The handler to run {@link #onChange} on, or null if none.
*/
public ContentObserver(Handler handler) {
mHandler = handler;
@@ -86,7 +44,7 @@ public abstract class ContentObserver {
* {@hide}
*/
public IContentObserver getContentObserver() {
- synchronized(lock) {
+ synchronized (mLock) {
if (mTransport == null) {
mTransport = new Transport(this);
}
@@ -101,8 +59,8 @@ public abstract class ContentObserver {
* {@hide}
*/
public IContentObserver releaseContentObserver() {
- synchronized(lock) {
- Transport oldTransport = mTransport;
+ synchronized (mLock) {
+ final Transport oldTransport = mTransport;
if (oldTransport != null) {
oldTransport.releaseContentObserver();
mTransport = null;
@@ -112,27 +70,134 @@ public abstract class ContentObserver {
}
/**
- * Returns true if this observer is interested in notifications for changes
- * made through the cursor the observer is registered with.
+ * Returns true if this observer is interested receiving self-change notifications.
+ *
+ * Subclasses should override this method to indicate whether the observer
+ * is interested in receiving notifications for changes that it made to the
+ * content itself.
+ *
+ * @return True if self-change notifications should be delivered to the observer.
*/
public boolean deliverSelfNotifications() {
return false;
}
/**
- * This method is called when a change occurs to the cursor that
- * is being observed.
- *
- * @param selfChange true if the update was caused by a call to <code>commit</code> on the
- * cursor that is being observed.
+ * This method is called when a content change occurs.
+ * <p>
+ * Subclasses should override this method to handle content changes.
+ * </p>
+ *
+ * @param selfChange True if this is a self-change notification.
*/
- public void onChange(boolean selfChange) {}
+ public void onChange(boolean selfChange) {
+ // Do nothing. Subclass should override.
+ }
+ /**
+ * This method is called when a content change occurs.
+ * Includes the changed content Uri when available.
+ * <p>
+ * Subclasses should override this method to handle content changes.
+ * To ensure correct operation on older versions of the framework that
+ * did not provide a Uri argument, applications should also implement
+ * the {@link #onChange(boolean)} overload of this method whenever they
+ * implement the {@link #onChange(boolean, Uri)} overload.
+ * </p><p>
+ * Example implementation:
+ * <pre><code>
+ * // Implement the onChange(boolean) method to delegate the change notification to
+ * // the onChange(boolean, Uri) method to ensure correct operation on older versions
+ * // of the framework that did not have the onChange(boolean, Uri) method.
+ * {@literal @Override}
+ * public void onChange(boolean selfChange) {
+ * onChange(selfChange, null);
+ * }
+ *
+ * // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument.
+ * {@literal @Override}
+ * public void onChange(boolean selfChange, Uri uri) {
+ * // Handle change.
+ * }
+ * </code></pre>
+ * </p>
+ *
+ * @param selfChange True if this is a self-change notification.
+ * @param uri The Uri of the changed content, or null if unknown.
+ */
+ public void onChange(boolean selfChange, Uri uri) {
+ onChange(selfChange);
+ }
+
+ /**
+ * Dispatches a change notification to the observer.
+ * <p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver} constructor,
+ * then a call to the {@link #onChange} method is posted to the handler's message queue.
+ * Otherwise, the {@link #onChange} method is invoked immediately on this thread.
+ * </p>
+ *
+ * @param selfChange True if this is a self-change notification.
+ *
+ * @deprecated Use {@link #dispatchChange(boolean, Uri)} instead.
+ */
+ @Deprecated
public final void dispatchChange(boolean selfChange) {
+ dispatchChange(selfChange, null);
+ }
+
+ /**
+ * Dispatches a change notification to the observer.
+ * Includes the changed content Uri when available.
+ * <p>
+ * If a {@link Handler} was supplied to the {@link ContentObserver} constructor,
+ * then a call to the {@link #onChange} method is posted to the handler's message queue.
+ * Otherwise, the {@link #onChange} method is invoked immediately on this thread.
+ * </p>
+ *
+ * @param selfChange True if this is a self-change notification.
+ * @param uri The Uri of the changed content, or null if unknown.
+ */
+ public final void dispatchChange(boolean selfChange, Uri uri) {
if (mHandler == null) {
- onChange(selfChange);
+ onChange(selfChange, uri);
} else {
- mHandler.post(new NotificationRunnable(selfChange));
+ mHandler.post(new NotificationRunnable(selfChange, uri));
+ }
+ }
+
+ private final class NotificationRunnable implements Runnable {
+ private final boolean mSelfChange;
+ private final Uri mUri;
+
+ public NotificationRunnable(boolean selfChange, Uri uri) {
+ mSelfChange = selfChange;
+ mUri = uri;
+ }
+
+ @Override
+ public void run() {
+ ContentObserver.this.onChange(mSelfChange, mUri);
+ }
+ }
+
+ private static final class Transport extends IContentObserver.Stub {
+ private ContentObserver mContentObserver;
+
+ public Transport(ContentObserver contentObserver) {
+ mContentObserver = contentObserver;
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ ContentObserver contentObserver = mContentObserver;
+ if (contentObserver != null) {
+ contentObserver.dispatchChange(selfChange, uri);
+ }
+ }
+
+ public void releaseContentObserver() {
+ mContentObserver = null;
}
}
}
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index a9a71cf..59ec89d 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -341,6 +341,7 @@ public interface Cursor {
* Deactivates the Cursor, making all calls on it fail until {@link #requery} is called.
* Inactive Cursors use fewer resources than active Cursors.
* Calling {@link #requery} will make the cursor active again.
+ * @deprecated Since {@link #requery()} is deprecated, so too is this.
*/
void deactivate();
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index aa0f61e..167278a 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -16,6 +16,7 @@
package android.database;
+import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -78,9 +79,9 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
@Override
- public void onChange(boolean selfChange) {
+ public void onChange(boolean selfChange, Uri uri) {
try {
- mRemote.onChange(selfChange);
+ mRemote.onChange(selfChange, uri);
} catch (RemoteException ex) {
// Do nothing, the far side is dead
}
diff --git a/core/java/android/database/DataSetObservable.java b/core/java/android/database/DataSetObservable.java
index 51c72c1..ca77a13 100644
--- a/core/java/android/database/DataSetObservable.java
+++ b/core/java/android/database/DataSetObservable.java
@@ -17,13 +17,15 @@
package android.database;
/**
- * A specialization of Observable for DataSetObserver that provides methods for
- * invoking the various callback methods of DataSetObserver.
+ * A specialization of {@link Observable} for {@link DataSetObserver}
+ * that provides methods for sending notifications to a list of
+ * {@link DataSetObserver} objects.
*/
public class DataSetObservable extends Observable<DataSetObserver> {
/**
- * Invokes onChanged on each observer. Called when the data set being observed has
- * changed, and which when read contains the new state of the data.
+ * Invokes {@link DataSetObserver#onChanged} on each observer.
+ * Called when the contents of the data set have changed. The recipient
+ * will obtain the new contents the next time it queries the data set.
*/
public void notifyChanged() {
synchronized(mObservers) {
@@ -38,8 +40,9 @@ public class DataSetObservable extends Observable<DataSetObserver> {
}
/**
- * Invokes onInvalidated on each observer. Called when the data set being monitored
- * has changed such that it is no longer valid.
+ * Invokes {@link DataSetObserver#onInvalidated} on each observer.
+ * Called when the data set is no longer valid and cannot be queried again,
+ * such as when the data set has been closed.
*/
public void notifyInvalidated() {
synchronized (mObservers) {
diff --git a/core/java/android/database/IContentObserver.aidl b/core/java/android/database/IContentObserver.aidl
index ac2f975..13aff05 100755
--- a/core/java/android/database/IContentObserver.aidl
+++ b/core/java/android/database/IContentObserver.aidl
@@ -17,6 +17,8 @@
package android.database;
+import android.net.Uri;
+
/**
* @hide
*/
@@ -27,5 +29,5 @@ interface IContentObserver
* observed. selfUpdate is true if the update was caused by a call to
* commit on the cursor that is being observed.
*/
- oneway void onChange(boolean selfUpdate);
+ oneway void onChange(boolean selfUpdate, in Uri uri);
}
diff --git a/core/java/android/database/Observable.java b/core/java/android/database/Observable.java
index b6fecab..aff32db 100644
--- a/core/java/android/database/Observable.java
+++ b/core/java/android/database/Observable.java
@@ -19,7 +19,12 @@ package android.database;
import java.util.ArrayList;
/**
- * Provides methods for (un)registering arbitrary observers in an ArrayList.
+ * Provides methods for registering or unregistering arbitrary observers in an {@link ArrayList}.
+ *
+ * This abstract class is intended to be subclassed and specialized to maintain
+ * a registry of observers of specific types and dispatch notifications to them.
+ *
+ * @param T The observer type.
*/
public abstract class Observable<T> {
/**
@@ -66,13 +71,13 @@ public abstract class Observable<T> {
mObservers.remove(index);
}
}
-
+
/**
- * Remove all registered observer
+ * Remove all registered observers.
*/
public void unregisterAll() {
synchronized(mObservers) {
mObservers.clear();
- }
+ }
}
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index e45d66d..72f62fd 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -74,10 +74,17 @@ import java.util.regex.Pattern;
* queues.
* </p>
*
+ * <h2>Reentrance</h2>
+ * <p>
+ * This class must tolerate reentrant execution of SQLite operations because
+ * triggers may call custom SQLite functions that perform additional queries.
+ * </p>
+ *
* @hide
*/
public final class SQLiteConnection {
private static final String TAG = "SQLiteConnection";
+ private static final boolean DEBUG = false;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
@@ -187,8 +194,6 @@ public final class SQLiteConnection {
}
private void open() {
- SQLiteGlobal.initializeOnce();
-
mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
mConfiguration.label,
SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
@@ -205,13 +210,13 @@ public final class SQLiteConnection {
}
if (mConnectionPtr != 0) {
- mRecentOperations.beginOperation("close", null, null);
+ final int cookie = mRecentOperations.beginOperation("close", null, null);
try {
mPreparedStatementCache.evictAll();
nativeClose(mConnectionPtr);
mConnectionPtr = 0;
} finally {
- mRecentOperations.endOperation();
+ mRecentOperations.endOperation(cookie);
}
}
}
@@ -304,9 +309,9 @@ public final class SQLiteConnection {
throw new IllegalArgumentException("sql must not be null.");
}
- mRecentOperations.beginOperation("prepare", sql, null);
+ final int cookie = mRecentOperations.beginOperation("prepare", sql, null);
try {
- PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(sql);
try {
if (outStatementInfo != null) {
outStatementInfo.numParameters = statement.mNumParameters;
@@ -328,10 +333,10 @@ public final class SQLiteConnection {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
- mRecentOperations.failOperation(ex);
+ mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- mRecentOperations.endOperation();
+ mRecentOperations.endOperation(cookie);
}
}
@@ -349,9 +354,9 @@ public final class SQLiteConnection {
throw new IllegalArgumentException("sql must not be null.");
}
- mRecentOperations.beginOperation("execute", sql, bindArgs);
+ final int cookie = mRecentOperations.beginOperation("execute", sql, bindArgs);
try {
- PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -361,10 +366,10 @@ public final class SQLiteConnection {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
- mRecentOperations.failOperation(ex);
+ mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- mRecentOperations.endOperation();
+ mRecentOperations.endOperation(cookie);
}
}
@@ -384,9 +389,9 @@ public final class SQLiteConnection {
throw new IllegalArgumentException("sql must not be null.");
}
- mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
+ final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
try {
- PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -396,10 +401,10 @@ public final class SQLiteConnection {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
- mRecentOperations.failOperation(ex);
+ mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- mRecentOperations.endOperation();
+ mRecentOperations.endOperation(cookie);
}
}
@@ -419,9 +424,9 @@ public final class SQLiteConnection {
throw new IllegalArgumentException("sql must not be null.");
}
- mRecentOperations.beginOperation("executeForString", sql, bindArgs);
+ final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs);
try {
- PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -431,10 +436,10 @@ public final class SQLiteConnection {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
- mRecentOperations.failOperation(ex);
+ mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- mRecentOperations.endOperation();
+ mRecentOperations.endOperation(cookie);
}
}
@@ -456,9 +461,10 @@ public final class SQLiteConnection {
throw new IllegalArgumentException("sql must not be null.");
}
- mRecentOperations.beginOperation("executeForBlobFileDescriptor", sql, bindArgs);
+ final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor",
+ sql, bindArgs);
try {
- PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -470,10 +476,10 @@ public final class SQLiteConnection {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
- mRecentOperations.failOperation(ex);
+ mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- mRecentOperations.endOperation();
+ mRecentOperations.endOperation(cookie);
}
}
@@ -493,9 +499,10 @@ public final class SQLiteConnection {
throw new IllegalArgumentException("sql must not be null.");
}
- mRecentOperations.beginOperation("executeForChangedRowCount", sql, bindArgs);
+ final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount",
+ sql, bindArgs);
try {
- PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -506,10 +513,10 @@ public final class SQLiteConnection {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
- mRecentOperations.failOperation(ex);
+ mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- mRecentOperations.endOperation();
+ mRecentOperations.endOperation(cookie);
}
}
@@ -529,9 +536,10 @@ public final class SQLiteConnection {
throw new IllegalArgumentException("sql must not be null.");
}
- mRecentOperations.beginOperation("executeForLastInsertedRowId", sql, bindArgs);
+ final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId",
+ sql, bindArgs);
try {
- PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -542,10 +550,10 @@ public final class SQLiteConnection {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
- mRecentOperations.failOperation(ex);
+ mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- mRecentOperations.endOperation();
+ mRecentOperations.endOperation(cookie);
}
}
@@ -581,9 +589,10 @@ public final class SQLiteConnection {
int actualPos = -1;
int countedRows = -1;
int filledRows = -1;
- mRecentOperations.beginOperation("executeForCursorWindow", sql, bindArgs);
+ final int cookie = mRecentOperations.beginOperation("executeForCursorWindow",
+ sql, bindArgs);
try {
- PreparedStatement statement = acquirePreparedStatement(sql);
+ final PreparedStatement statement = acquirePreparedStatement(sql);
try {
throwIfStatementForbidden(statement);
bindArguments(statement, bindArgs);
@@ -600,11 +609,11 @@ public final class SQLiteConnection {
releasePreparedStatement(statement);
}
} catch (RuntimeException ex) {
- mRecentOperations.failOperation(ex);
+ mRecentOperations.failOperation(cookie, ex);
throw ex;
} finally {
- if (mRecentOperations.endOperationDeferLog()) {
- mRecentOperations.logOperation("window='" + window
+ if (mRecentOperations.endOperationDeferLog(cookie)) {
+ mRecentOperations.logOperation(cookie, "window='" + window
+ "', startPos=" + startPos
+ ", actualPos=" + actualPos
+ ", filledRows=" + filledRows
@@ -615,8 +624,15 @@ public final class SQLiteConnection {
private PreparedStatement acquirePreparedStatement(String sql) {
PreparedStatement statement = mPreparedStatementCache.get(sql);
+ boolean skipCache = false;
if (statement != null) {
- return statement;
+ if (!statement.mInUse) {
+ return statement;
+ }
+ // The statement is already in the cache but is in use (this statement appears
+ // to be not only re-entrant but recursive!). So prepare a new copy of the
+ // statement but do not cache it.
+ skipCache = true;
}
final int statementPtr = nativePrepareStatement(mConnectionPtr, sql);
@@ -625,7 +641,7 @@ public final class SQLiteConnection {
final int type = DatabaseUtils.getSqlStatementType(sql);
final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
- if (isCacheable(type)) {
+ if (!skipCache && isCacheable(type)) {
mPreparedStatementCache.put(sql, statement);
statement.mInCache = true;
}
@@ -637,31 +653,38 @@ public final class SQLiteConnection {
}
throw ex;
}
+ statement.mInUse = true;
return statement;
}
private void releasePreparedStatement(PreparedStatement statement) {
+ statement.mInUse = false;
if (statement.mInCache) {
try {
nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
} catch (SQLiteException ex) {
- // The statement could not be reset due to an error.
- // The entryRemoved() callback for the cache will recursively call
- // releasePreparedStatement() again, but this time mInCache will be false
- // so the statement will be finalized and recycled.
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- Log.v(TAG, "Could not reset prepared statement due to an exception. "
+ // The statement could not be reset due to an error. Remove it from the cache.
+ // When remove() is called, the cache will invoke its entryRemoved() callback,
+ // which will in turn call finalizePreparedStatement() to finalize and
+ // recycle the statement.
+ if (DEBUG) {
+ Log.d(TAG, "Could not reset prepared statement due to an exception. "
+ "Removing it from the cache. SQL: "
+ trimSqlForDisplay(statement.mSql), ex);
}
+
mPreparedStatementCache.remove(statement.mSql);
}
} else {
- nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
- recyclePreparedStatement(statement);
+ finalizePreparedStatement(statement);
}
}
+ private void finalizePreparedStatement(PreparedStatement statement) {
+ nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
+ recyclePreparedStatement(statement);
+ }
+
private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
final int count = bindArgs != null ? bindArgs.length : 0;
if (count != statement.mNumParameters) {
@@ -735,9 +758,10 @@ public final class SQLiteConnection {
* Dumps debugging information about this connection.
*
* @param printer The printer to receive the dump, not null.
+ * @param verbose True to dump more verbose information.
*/
- public void dump(Printer printer) {
- dumpUnsafe(printer);
+ public void dump(Printer printer, boolean verbose) {
+ dumpUnsafe(printer, verbose);
}
/**
@@ -752,15 +776,21 @@ public final class SQLiteConnection {
* it should not crash. This is ok as it is only used for diagnostic purposes.
*
* @param printer The printer to receive the dump, not null.
+ * @param verbose True to dump more verbose information.
*/
- void dumpUnsafe(Printer printer) {
+ void dumpUnsafe(Printer printer, boolean verbose) {
printer.println("Connection #" + mConnectionId + ":");
+ if (verbose) {
+ printer.println(" connectionPtr: 0x" + Integer.toHexString(mConnectionPtr));
+ }
printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
- printer.println(" connectionPtr: 0x" + Integer.toHexString(mConnectionPtr));
printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
mRecentOperations.dump(printer);
- mPreparedStatementCache.dump(printer);
+
+ if (verbose) {
+ mPreparedStatementCache.dump(printer);
+ }
}
/**
@@ -917,6 +947,12 @@ public final class SQLiteConnection {
// True if the statement is in the cache.
public boolean mInCache;
+
+ // True if the statement is in use (currently executing).
+ // We need this flag because due to the use of custom functions in triggers, it's
+ // possible for SQLite calls to be re-entrant. Consequently we need to prevent
+ // in use statements from being finalized until they are no longer in use.
+ public boolean mInUse;
}
private final class PreparedStatementCache
@@ -929,7 +965,9 @@ public final class SQLiteConnection {
protected void entryRemoved(boolean evicted, String key,
PreparedStatement oldValue, PreparedStatement newValue) {
oldValue.mInCache = false;
- releasePreparedStatement(oldValue);
+ if (!oldValue.mInUse) {
+ finalizePreparedStatement(oldValue);
+ }
}
public void dump(Printer printer) {
@@ -957,12 +995,15 @@ public final class SQLiteConnection {
}
private static final class OperationLog {
- private static final int MAX_RECENT_OPERATIONS = 10;
+ private static final int MAX_RECENT_OPERATIONS = 20;
+ private static final int COOKIE_GENERATION_SHIFT = 8;
+ private static final int COOKIE_INDEX_MASK = 0xff;
private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
private int mIndex;
+ private int mGeneration;
- public void beginOperation(String kind, String sql, Object[] bindArgs) {
+ public int beginOperation(String kind, String sql, Object[] bindArgs) {
synchronized (mOperations) {
final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
Operation operation = mOperations[index];
@@ -995,47 +1036,54 @@ public final class SQLiteConnection {
}
}
}
+ operation.mCookie = newOperationCookieLocked(index);
mIndex = index;
+ return operation.mCookie;
}
}
- public void failOperation(Exception ex) {
+ public void failOperation(int cookie, Exception ex) {
synchronized (mOperations) {
- final Operation operation = mOperations[mIndex];
- operation.mException = ex;
+ final Operation operation = getOperationLocked(cookie);
+ if (operation != null) {
+ operation.mException = ex;
+ }
}
}
- public boolean endOperationDeferLog() {
+ public void endOperation(int cookie) {
synchronized (mOperations) {
- return endOperationDeferLogLocked();
+ if (endOperationDeferLogLocked(cookie)) {
+ logOperationLocked(cookie, null);
+ }
}
}
- private boolean endOperationDeferLogLocked() {
- final Operation operation = mOperations[mIndex];
- operation.mEndTime = System.currentTimeMillis();
- operation.mFinished = true;
- return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
- operation.mEndTime - operation.mStartTime);
+ public boolean endOperationDeferLog(int cookie) {
+ synchronized (mOperations) {
+ return endOperationDeferLogLocked(cookie);
+ }
}
- public void endOperation() {
+ public void logOperation(int cookie, String detail) {
synchronized (mOperations) {
- if (endOperationDeferLogLocked()) {
- logOperationLocked(null);
- }
+ logOperationLocked(cookie, detail);
}
}
- public void logOperation(String detail) {
- synchronized (mOperations) {
- logOperationLocked(detail);
+ private boolean endOperationDeferLogLocked(int cookie) {
+ final Operation operation = getOperationLocked(cookie);
+ if (operation != null) {
+ operation.mEndTime = System.currentTimeMillis();
+ operation.mFinished = true;
+ return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
+ operation.mEndTime - operation.mStartTime);
}
+ return false;
}
- private void logOperationLocked(String detail) {
- final Operation operation = mOperations[mIndex];
+ private void logOperationLocked(int cookie, String detail) {
+ final Operation operation = getOperationLocked(cookie);
StringBuilder msg = new StringBuilder();
operation.describe(msg);
if (detail != null) {
@@ -1044,6 +1092,17 @@ public final class SQLiteConnection {
Log.d(TAG, msg.toString());
}
+ private int newOperationCookieLocked(int index) {
+ final int generation = mGeneration++;
+ return generation << COOKIE_GENERATION_SHIFT | index;
+ }
+
+ private Operation getOperationLocked(int cookie) {
+ final int index = cookie & COOKIE_INDEX_MASK;
+ final Operation operation = mOperations[index];
+ return operation.mCookie == cookie ? operation : null;
+ }
+
public String describeCurrentOperation() {
synchronized (mOperations) {
final Operation operation = mOperations[mIndex];
@@ -1097,6 +1156,7 @@ public final class SQLiteConnection {
public ArrayList<Object> mBindArgs;
public boolean mFinished;
public Exception mException;
+ public int mCookie;
public void describe(StringBuilder msg) {
msg.append(mKind);
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index b88bfee..5469213 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -833,8 +833,9 @@ public final class SQLiteConnectionPool implements Closeable {
* Dumps debugging information about this connection pool.
*
* @param printer The printer to receive the dump, not null.
+ * @param verbose True to dump more verbose information.
*/
- public void dump(Printer printer) {
+ public void dump(Printer printer, boolean verbose) {
Printer indentedPrinter = PrefixPrinter.create(printer, " ");
synchronized (mLock) {
printer.println("Connection pool for " + mConfiguration.path + ":");
@@ -843,7 +844,7 @@ public final class SQLiteConnectionPool implements Closeable {
printer.println(" Available primary connection:");
if (mAvailablePrimaryConnection != null) {
- mAvailablePrimaryConnection.dump(indentedPrinter);
+ mAvailablePrimaryConnection.dump(indentedPrinter, verbose);
} else {
indentedPrinter.println("<none>");
}
@@ -852,7 +853,7 @@ public final class SQLiteConnectionPool implements Closeable {
if (!mAvailableNonPrimaryConnections.isEmpty()) {
final int count = mAvailableNonPrimaryConnections.size();
for (int i = 0; i < count; i++) {
- mAvailableNonPrimaryConnections.get(i).dump(indentedPrinter);
+ mAvailableNonPrimaryConnections.get(i).dump(indentedPrinter, verbose);
}
} else {
indentedPrinter.println("<none>");
@@ -863,7 +864,7 @@ public final class SQLiteConnectionPool implements Closeable {
for (Map.Entry<SQLiteConnection, Boolean> entry :
mAcquiredConnections.entrySet()) {
final SQLiteConnection connection = entry.getKey();
- connection.dumpUnsafe(indentedPrinter);
+ connection.dumpUnsafe(indentedPrinter, verbose);
indentedPrinter.println(" Pending reconfiguration: " + entry.getValue());
}
} else {
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 9dcb498..82bb23e 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -65,8 +65,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
* interface. For a query such as: {@code SELECT name, birth, phone FROM
* myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
* phone) would be in the projection argument and everything from
- * {@code FROM} onward would be in the params argument. This constructor
- * has package scope.
+ * {@code FROM} onward would be in the params argument.
*
* @param db a reference to a Database object that is already constructed
* and opened. This param is not used any longer
@@ -86,8 +85,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
* interface. For a query such as: {@code SELECT name, birth, phone FROM
* myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
* phone) would be in the projection argument and everything from
- * {@code FROM} onward would be in the params argument. This constructor
- * has package scope.
+ * {@code FROM} onward would be in the params argument.
*
* @param editTable the name of the table used for this query
* @param query the {@link SQLiteQuery} object associated with this cursor object.
@@ -269,7 +267,6 @@ public class SQLiteCursor extends AbstractWindowedCursor {
mStackTrace);
}
close();
- SQLiteDebug.notifyActiveCursorFinalized();
}
} finally {
super.finalize();
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 377a680..9cb6480 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1665,17 +1665,17 @@ public class SQLiteDatabase extends SQLiteClosable {
* Dump detailed information about all open databases in the current process.
* Used by bug report.
*/
- static void dumpAll(Printer printer) {
+ static void dumpAll(Printer printer, boolean verbose) {
for (SQLiteDatabase db : getActiveDatabases()) {
- db.dump(printer);
+ db.dump(printer, verbose);
}
}
- private void dump(Printer printer) {
+ private void dump(Printer printer, boolean verbose) {
synchronized (mLock) {
if (mConnectionPoolLocked != null) {
printer.println("");
- mConnectionPoolLocked.dump(printer);
+ mConnectionPoolLocked.dump(printer, verbose);
}
}
}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index d87c3e4..204483d 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -29,6 +29,8 @@ import android.util.Printer;
* {@hide}
*/
public final class SQLiteDebug {
+ private static native void nativeGetPagerStats(PagerStats stats);
+
/**
* Controls the printing of informational SQL log messages.
*/
@@ -49,31 +51,6 @@ public final class SQLiteDebug {
Log.isLoggable("SQLiteTime", Log.VERBOSE);
/**
- * Controls the printing of compiled-sql-statement cache stats.
- */
- public static final boolean DEBUG_SQL_CACHE =
- Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);
-
- /**
- * Controls the stack trace reporting of active cursors being
- * finalized.
- */
- public static final boolean DEBUG_ACTIVE_CURSOR_FINALIZATION =
- Log.isLoggable("SQLiteCursorClosing", Log.VERBOSE);
-
- /**
- * Controls the tracking of time spent holding the database lock.
- */
- public static final boolean DEBUG_LOCK_TIME_TRACKING =
- Log.isLoggable("SQLiteLockTime", Log.VERBOSE);
-
- /**
- * Controls the printing of stack traces when tracking the time spent holding the database lock.
- */
- public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE =
- Log.isLoggable("SQLiteLockStackTrace", Log.VERBOSE);
-
- /**
* True to enable database performance testing instrumentation.
* @hide
*/
@@ -98,30 +75,9 @@ public final class SQLiteDebug {
/**
* Contains statistics about the active pagers in the current process.
*
- * @see #getPagerStats(PagerStats)
+ * @see #nativeGetPagerStats(PagerStats)
*/
public static class PagerStats {
- /** The total number of bytes in all pagers in the current process
- * @deprecated not used any longer
- */
- @Deprecated
- public long totalBytes;
- /** The number of bytes in referenced pages in all pagers in the current process
- * @deprecated not used any longer
- * */
- @Deprecated
- public long referencedBytes;
- /** The number of bytes in all database files opened in the current process
- * @deprecated not used any longer
- */
- @Deprecated
- public long databaseBytes;
- /** The number of pagers opened in the current process
- * @deprecated not used any longer
- */
- @Deprecated
- public int numPagers;
-
/** the current amount of memory checked out by sqlite using sqlite3_malloc().
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
@@ -134,7 +90,7 @@ public final class SQLiteDebug {
* that overflowed because no space was left in the page cache.
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
- public int pageCacheOverflo;
+ public int pageCacheOverflow;
/** records the largest memory allocation request handed to sqlite3.
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
@@ -182,7 +138,7 @@ public final class SQLiteDebug {
*/
public static PagerStats getDatabaseInfo() {
PagerStats stats = new PagerStats();
- getPagerStats(stats);
+ nativeGetPagerStats(stats);
stats.dbStats = SQLiteDatabase.getDbStats();
return stats;
}
@@ -190,52 +146,16 @@ public final class SQLiteDebug {
/**
* Dumps detailed information about all databases used by the process.
* @param printer The printer for dumping database state.
+ * @param args Command-line arguments supplied to dumpsys dbinfo
*/
public static void dump(Printer printer, String[] args) {
- SQLiteDatabase.dumpAll(printer);
- }
-
- /**
- * Gathers statistics about all pagers in the current process.
- */
- public static native void getPagerStats(PagerStats stats);
-
- /**
- * Returns the size of the SQLite heap.
- * @return The size of the SQLite heap in bytes.
- */
- public static native long getHeapSize();
-
- /**
- * Returns the amount of allocated memory in the SQLite heap.
- * @return The allocated size in bytes.
- */
- public static native long getHeapAllocatedSize();
-
- /**
- * Returns the amount of free memory in the SQLite heap.
- * @return The freed size in bytes.
- */
- public static native long getHeapFreeSize();
-
- /**
- * Determines the number of dirty belonging to the SQLite
- * heap segments of this process. pages[0] returns the number of
- * shared pages, pages[1] returns the number of private pages
- */
- public static native void getHeapDirtyPages(int[] pages);
-
- private static int sNumActiveCursorsFinalized = 0;
-
- /**
- * Returns the number of active cursors that have been finalized. This depends on the GC having
- * run but is still useful for tests.
- */
- public static int getNumActiveCursorsFinalized() {
- return sNumActiveCursorsFinalized;
- }
+ boolean verbose = false;
+ for (String arg : args) {
+ if (arg.equals("-v")) {
+ verbose = true;
+ }
+ }
- static synchronized void notifyActiveCursorFinalized() {
- sNumActiveCursorsFinalized++;
+ SQLiteDatabase.dumpAll(printer, verbose);
}
}
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 5e129be..dbefd63 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -22,57 +22,35 @@ import android.os.StatFs;
* Provides access to SQLite functions that affect all database connection,
* such as memory management.
*
+ * The native code associated with SQLiteGlobal is also sets global configuration options
+ * using sqlite3_config() then calls sqlite3_initialize() to ensure that the SQLite
+ * library is properly initialized exactly once before any other framework or application
+ * code has a chance to run.
+ *
+ * Verbose SQLite logging is enabled if the "log.tag.SQLiteLog" property is set to "V".
+ * (per {@link SQLiteDebug#DEBUG_SQL_LOG}).
+ *
* @hide
*/
public final class SQLiteGlobal {
private static final String TAG = "SQLiteGlobal";
private static final Object sLock = new Object();
- private static boolean sInitialized;
- private static int sSoftHeapLimit;
private static int sDefaultPageSize;
- private static native void nativeConfig(boolean verboseLog, int softHeapLimit);
- private static native int nativeReleaseMemory(int bytesToFree);
+ private static native int nativeReleaseMemory();
private SQLiteGlobal() {
}
/**
- * Initializes global SQLite settings the first time it is called.
- * Should be called before opening the first (or any) database.
- * Does nothing on repeated subsequent calls.
- */
- public static void initializeOnce() {
- synchronized (sLock) {
- if (!sInitialized) {
- sInitialized = true;
-
- // Limit to 8MB for now. This is 4 times the maximum cursor window
- // size, as has been used by the original code in SQLiteDatabase for
- // a long time.
- // TODO: We really do need to test whether this helps or hurts us.
- sSoftHeapLimit = 8 * 1024 * 1024;
-
- // Configure SQLite.
- nativeConfig(SQLiteDebug.DEBUG_SQL_LOG, sSoftHeapLimit);
- }
- }
- }
-
- /**
* Attempts to release memory by pruning the SQLite page cache and other
* internal data structures.
*
* @return The number of bytes that were freed.
*/
public static int releaseMemory() {
- synchronized (sLock) {
- if (!sInitialized) {
- return 0;
- }
- return nativeReleaseMemory(sSoftHeapLimit);
- }
+ return nativeReleaseMemory();
}
/**
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 31da7e4..46d9369 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -81,7 +81,8 @@ public abstract class SQLiteOpenHelper {
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
- * {@link #onUpgrade} will be used to upgrade the database
+ * {@link #onUpgrade} will be used to upgrade the database; if the database is
+ * newer, {@link #onDowngrade} will be used to downgrade the database
* @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
* corruption.
*/
@@ -100,7 +101,7 @@ public abstract class SQLiteOpenHelper {
}
/**
- * Return the name of the SQLite database being opened, as given tp
+ * Return the name of the SQLite database being opened, as given to
* the constructor.
*/
public String getDatabaseName() {
@@ -297,7 +298,7 @@ public abstract class SQLiteOpenHelper {
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
/**
- * Called when the database needs to be downgraded. This is stricly similar to
+ * Called when the database needs to be downgraded. This is strictly similar to
* onUpgrade() method, but is called whenever current version is newer than requested one.
* However, this method is not abstract, so it is not mandatory for a customer to
* implement it. If not overridden, default implementation will reject downgrade and
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index 61fe45a..a933051 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -150,6 +150,12 @@ import android.os.ParcelFileDescriptor;
* A query that works well on 100 rows may struggle with 10,000.</li>
* </ul>
*
+ * <h2>Reentrance</h2>
+ * <p>
+ * This class must tolerate reentrant execution of SQLite operations because
+ * triggers may call custom SQLite functions that perform additional queries.
+ * </p>
+ *
* TODO: Support timeouts on all possibly blocking operations.
*
* @hide
@@ -159,6 +165,7 @@ public final class SQLiteSession {
private SQLiteConnection mConnection;
private int mConnectionFlags;
+ private int mConnectionUseCount;
private Transaction mTransactionPool;
private Transaction mTransactionStack;
@@ -289,7 +296,9 @@ public final class SQLiteSession {
private void beginTransactionUnchecked(int transactionMode,
SQLiteTransactionListener transactionListener, int connectionFlags) {
- acquireConnectionIfNoTransaction(null, connectionFlags); // might throw
+ if (mTransactionStack == null) {
+ acquireConnection(null, connectionFlags); // might throw
+ }
try {
// Set up the transaction such that we can back out safely
// in case we fail part way.
@@ -325,7 +334,9 @@ public final class SQLiteSession {
transaction.mParent = mTransactionStack;
mTransactionStack = transaction;
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ if (mTransactionStack == null) {
+ releaseConnection(); // might throw
+ }
}
}
@@ -408,7 +419,7 @@ public final class SQLiteSession {
mConnection.execute("ROLLBACK;", null); // might throw
}
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -534,11 +545,11 @@ public final class SQLiteSession {
throw new IllegalArgumentException("sql must not be null.");
}
- acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ acquireConnection(sql, connectionFlags); // might throw
try {
mConnection.prepare(sql, outStatementInfo); // might throw
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -562,11 +573,11 @@ public final class SQLiteSession {
return;
}
- acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ acquireConnection(sql, connectionFlags); // might throw
try {
mConnection.execute(sql, bindArgs); // might throw
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -592,11 +603,11 @@ public final class SQLiteSession {
return 0;
}
- acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ acquireConnection(sql, connectionFlags); // might throw
try {
return mConnection.executeForLong(sql, bindArgs); // might throw
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -622,11 +633,11 @@ public final class SQLiteSession {
return null;
}
- acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ acquireConnection(sql, connectionFlags); // might throw
try {
return mConnection.executeForString(sql, bindArgs); // might throw
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -655,11 +666,11 @@ public final class SQLiteSession {
return null;
}
- acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ acquireConnection(sql, connectionFlags); // might throw
try {
return mConnection.executeForBlobFileDescriptor(sql, bindArgs); // might throw
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -685,11 +696,11 @@ public final class SQLiteSession {
return 0;
}
- acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ acquireConnection(sql, connectionFlags); // might throw
try {
return mConnection.executeForChangedRowCount(sql, bindArgs); // might throw
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -715,11 +726,11 @@ public final class SQLiteSession {
return 0;
}
- acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ acquireConnection(sql, connectionFlags); // might throw
try {
return mConnection.executeForLastInsertedRowId(sql, bindArgs); // might throw
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -760,12 +771,12 @@ public final class SQLiteSession {
return 0;
}
- acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+ acquireConnection(sql, connectionFlags); // might throw
try {
return mConnection.executeForCursorWindow(sql, bindArgs,
window, startPos, requiredPos, countAllRows); // might throw
} finally {
- releaseConnectionIfNoTransaction(); // might throw
+ releaseConnection(); // might throw
}
}
@@ -807,16 +818,19 @@ public final class SQLiteSession {
return false;
}
- private void acquireConnectionIfNoTransaction(String sql, int connectionFlags) {
- if (mTransactionStack == null) {
- assert mConnection == null;
+ private void acquireConnection(String sql, int connectionFlags) {
+ if (mConnection == null) {
+ assert mConnectionUseCount == 0;
mConnection = mConnectionPool.acquireConnection(sql, connectionFlags); // might throw
mConnectionFlags = connectionFlags;
}
+ mConnectionUseCount += 1;
}
- private void releaseConnectionIfNoTransaction() {
- if (mTransactionStack == null && mConnection != null) {
+ private void releaseConnection() {
+ assert mConnection != null;
+ assert mConnectionUseCount > 0;
+ if (--mConnectionUseCount == 0) {
try {
mConnectionPool.releaseConnection(mConnection); // might throw
} finally {
diff --git a/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java
deleted file mode 100644
index bcf95e2..0000000
--- a/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-/**
- * Thrown if the database can't be closed because of some un-closed
- * Cursor or SQLiteStatement objects. Could happen when a thread is trying to close
- * the database while another thread still hasn't closed a Cursor on that database.
- * @hide
- */
-public class SQLiteUnfinalizedObjectsException extends SQLiteException {
- public SQLiteUnfinalizedObjectsException() {}
-
- public SQLiteUnfinalizedObjectsException(String error) {
- super(error);
- }
-}
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index 10c1195..23ae21b 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -100,6 +100,9 @@ public class ExtractEditText extends EditText {
@Override public boolean onTextContextMenuItem(int id) {
if (mIME != null && mIME.onExtractTextContextMenuItem(id)) {
+ // Mode was started on Extracted, needs to be stopped here.
+ // Cut and paste will change the text, which stops selection mode.
+ if (id == android.R.id.copy) stopSelectionActionMode();
return true;
}
return super.onTextContextMenuItem(id);
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index e8f60b4..7a1ef66 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -102,6 +102,15 @@ public class NetworkStats implements Parcelable {
this.operations = operations;
}
+ public boolean isNegative() {
+ return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
+ }
+
+ public boolean isEmpty() {
+ return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
+ && operations == 0;
+ }
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
@@ -343,6 +352,7 @@ public class NetworkStats implements Parcelable {
* on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
* since operation counts are at data layer.
*/
+ @Deprecated
public void spliceOperationsFrom(NetworkStats stats) {
for (int i = 0; i < size; i++) {
final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
@@ -397,7 +407,7 @@ public class NetworkStats implements Parcelable {
* Return total of all fields represented by this snapshot object.
*/
public Entry getTotal(Entry recycle) {
- return getTotal(recycle, null, UID_ALL);
+ return getTotal(recycle, null, UID_ALL, false);
}
/**
@@ -405,7 +415,7 @@ public class NetworkStats implements Parcelable {
* the requested {@link #uid}.
*/
public Entry getTotal(Entry recycle, int limitUid) {
- return getTotal(recycle, null, limitUid);
+ return getTotal(recycle, null, limitUid, false);
}
/**
@@ -413,7 +423,11 @@ public class NetworkStats implements Parcelable {
* the requested {@link #iface}.
*/
public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
- return getTotal(recycle, limitIface, UID_ALL);
+ return getTotal(recycle, limitIface, UID_ALL, false);
+ }
+
+ public Entry getTotalIncludingTags(Entry recycle) {
+ return getTotal(recycle, null, UID_ALL, true);
}
/**
@@ -423,7 +437,8 @@ public class NetworkStats implements Parcelable {
* @param limitIface Set of {@link #iface} to include in total; or {@code
* null} to include all ifaces.
*/
- private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) {
+ private Entry getTotal(
+ Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) {
final Entry entry = recycle != null ? recycle : new Entry();
entry.iface = IFACE_ALL;
@@ -442,7 +457,7 @@ public class NetworkStats implements Parcelable {
if (matchesUid && matchesIface) {
// skip specific tags, since already counted in TAG_NONE
- if (tag[i] != TAG_NONE) continue;
+ if (tag[i] != TAG_NONE && !includeTags) continue;
entry.rxBytes += rxBytes[i];
entry.rxPackets += rxPackets[i];
@@ -460,7 +475,7 @@ public class NetworkStats implements Parcelable {
* time, and that none of them have disappeared.
*/
public NetworkStats subtract(NetworkStats right) {
- return subtract(this, right, null);
+ return subtract(this, right, null, null);
}
/**
@@ -471,12 +486,12 @@ public class NetworkStats implements Parcelable {
* If counters have rolled backwards, they are clamped to {@code 0} and
* reported to the given {@link NonMonotonicObserver}.
*/
- public static NetworkStats subtract(
- NetworkStats left, NetworkStats right, NonMonotonicObserver observer) {
+ public static <C> NetworkStats subtract(
+ NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie) {
long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime;
if (deltaRealtime < 0) {
if (observer != null) {
- observer.foundNonMonotonic(left, -1, right, -1);
+ observer.foundNonMonotonic(left, -1, right, -1, cookie);
}
deltaRealtime = 0;
}
@@ -510,7 +525,7 @@ public class NetworkStats implements Parcelable {
if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
|| entry.txPackets < 0 || entry.operations < 0) {
if (observer != null) {
- observer.foundNonMonotonic(left, i, right, j);
+ observer.foundNonMonotonic(left, i, right, j, cookie);
}
entry.rxBytes = Math.max(entry.rxBytes, 0);
entry.rxPackets = Math.max(entry.rxPackets, 0);
@@ -663,8 +678,8 @@ public class NetworkStats implements Parcelable {
}
};
- public interface NonMonotonicObserver {
+ public interface NonMonotonicObserver<C> {
public void foundNonMonotonic(
- NetworkStats left, int leftIndex, NetworkStats right, int rightIndex);
+ NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
}
}
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 8c01331..faf8a3f 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -26,16 +26,18 @@ import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray;
import static android.net.NetworkStatsHistory.Entry.UNKNOWN;
import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray;
import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray;
+import static com.android.internal.util.ArrayUtils.total;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.MathUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
import java.io.CharArrayWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
-import java.io.PrintWriter;
import java.net.ProtocolException;
import java.util.Arrays;
import java.util.Random;
@@ -74,6 +76,7 @@ public class NetworkStatsHistory implements Parcelable {
private long[] txBytes;
private long[] txPackets;
private long[] operations;
+ private long totalBytes;
public static class Entry {
public static final long UNKNOWN = -1;
@@ -106,6 +109,12 @@ public class NetworkStatsHistory implements Parcelable {
if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize];
if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize];
bucketCount = 0;
+ totalBytes = 0;
+ }
+
+ public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) {
+ this(bucketDuration, existing.estimateResizeBuckets(bucketDuration));
+ recordEntireHistory(existing);
}
public NetworkStatsHistory(Parcel in) {
@@ -118,6 +127,7 @@ public class NetworkStatsHistory implements Parcelable {
txPackets = readLongArray(in);
operations = readLongArray(in);
bucketCount = bucketStart.length;
+ totalBytes = in.readLong();
}
/** {@inheritDoc} */
@@ -130,6 +140,7 @@ public class NetworkStatsHistory implements Parcelable {
writeLongArray(out, txBytes, bucketCount);
writeLongArray(out, txPackets, bucketCount);
writeLongArray(out, operations, bucketCount);
+ out.writeLong(totalBytes);
}
public NetworkStatsHistory(DataInputStream in) throws IOException {
@@ -144,6 +155,7 @@ public class NetworkStatsHistory implements Parcelable {
txPackets = new long[bucketStart.length];
operations = new long[bucketStart.length];
bucketCount = bucketStart.length;
+ totalBytes = total(rxBytes) + total(txBytes);
break;
}
case VERSION_ADD_PACKETS:
@@ -158,6 +170,7 @@ public class NetworkStatsHistory implements Parcelable {
txPackets = readVarLongArray(in);
operations = readVarLongArray(in);
bucketCount = bucketStart.length;
+ totalBytes = total(rxBytes) + total(txBytes);
break;
}
default: {
@@ -208,6 +221,13 @@ public class NetworkStatsHistory implements Parcelable {
}
/**
+ * Return total bytes represented by this history.
+ */
+ public long getTotalBytes() {
+ return totalBytes;
+ }
+
+ /**
* Return index of bucket that contains or is immediately before the
* requested time.
*/
@@ -266,13 +286,16 @@ public class NetworkStatsHistory implements Parcelable {
* distribute across internal buckets, creating new buckets as needed.
*/
public void recordData(long start, long end, NetworkStats.Entry entry) {
- if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 || entry.txPackets < 0
- || entry.operations < 0) {
+ long rxBytes = entry.rxBytes;
+ long rxPackets = entry.rxPackets;
+ long txBytes = entry.txBytes;
+ long txPackets = entry.txPackets;
+ long operations = entry.operations;
+
+ if (entry.isNegative()) {
throw new IllegalArgumentException("tried recording negative data");
}
- if (entry.rxBytes == 0 && entry.rxPackets == 0 && entry.txBytes == 0 && entry.txPackets == 0
- && entry.operations == 0) {
- // nothing to record; skip
+ if (entry.isEmpty()) {
return;
}
@@ -295,21 +318,23 @@ public class NetworkStatsHistory implements Parcelable {
if (overlap <= 0) continue;
// integer math each time is faster than floating point
- final long fracRxBytes = entry.rxBytes * overlap / duration;
- final long fracRxPackets = entry.rxPackets * overlap / duration;
- final long fracTxBytes = entry.txBytes * overlap / duration;
- final long fracTxPackets = entry.txPackets * overlap / duration;
- final long fracOperations = entry.operations * overlap / duration;
+ final long fracRxBytes = rxBytes * overlap / duration;
+ final long fracRxPackets = rxPackets * overlap / duration;
+ final long fracTxBytes = txBytes * overlap / duration;
+ final long fracTxPackets = txPackets * overlap / duration;
+ final long fracOperations = operations * overlap / duration;
addLong(activeTime, i, overlap);
- addLong(rxBytes, i, fracRxBytes); entry.rxBytes -= fracRxBytes;
- addLong(rxPackets, i, fracRxPackets); entry.rxPackets -= fracRxPackets;
- addLong(txBytes, i, fracTxBytes); entry.txBytes -= fracTxBytes;
- addLong(txPackets, i, fracTxPackets); entry.txPackets -= fracTxPackets;
- addLong(operations, i, fracOperations); entry.operations -= fracOperations;
+ addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes;
+ addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets;
+ addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes;
+ addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets;
+ addLong(this.operations, i, fracOperations); operations -= fracOperations;
duration -= overlap;
}
+
+ totalBytes += entry.rxBytes + entry.txBytes;
}
/**
@@ -394,6 +419,7 @@ public class NetworkStatsHistory implements Parcelable {
/**
* Remove buckets older than requested cutoff.
*/
+ @Deprecated
public void removeBucketsBefore(long cutoff) {
int i;
for (i = 0; i < bucketCount; i++) {
@@ -415,6 +441,8 @@ public class NetworkStatsHistory implements Parcelable {
if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length);
if (operations != null) operations = Arrays.copyOfRange(operations, i, length);
bucketCount -= i;
+
+ // TODO: subtract removed values from totalBytes
}
}
@@ -527,19 +555,17 @@ public class NetworkStatsHistory implements Parcelable {
return (long) (start + (r.nextFloat() * (end - start)));
}
- public void dump(String prefix, PrintWriter pw, boolean fullHistory) {
- pw.print(prefix);
+ public void dump(IndentingPrintWriter pw, boolean fullHistory) {
pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration);
+ pw.increaseIndent();
final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32);
if (start > 0) {
- pw.print(prefix);
- pw.print(" (omitting "); pw.print(start); pw.println(" buckets)");
+ pw.print("(omitting "); pw.print(start); pw.println(" buckets)");
}
for (int i = start; i < bucketCount; i++) {
- pw.print(prefix);
- pw.print(" bucketStart="); pw.print(bucketStart[i]);
+ pw.print("bucketStart="); pw.print(bucketStart[i]);
if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); }
if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); }
if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); }
@@ -548,12 +574,14 @@ public class NetworkStatsHistory implements Parcelable {
if (operations != null) { pw.print(" operations="); pw.print(operations[i]); }
pw.println();
}
+
+ pw.decreaseIndent();
}
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
- dump("", new PrintWriter(writer), false);
+ dump(new IndentingPrintWriter(writer, " "), false);
return writer.toString();
}
@@ -579,6 +607,10 @@ public class NetworkStatsHistory implements Parcelable {
if (array != null) array[i] += value;
}
+ public int estimateResizeBuckets(long newBucketDuration) {
+ return (int) (size() * getBucketDuration() / newBucketDuration);
+ }
+
/**
* Utility methods for interacting with {@link DataInputStream} and
* {@link DataOutputStream}, mostly dealing with writing partial arrays.
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 418b82f..8ebfd8d 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -18,6 +18,7 @@ package android.net;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkIdentity.scrubSubscriberId;
import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
@@ -231,10 +232,13 @@ public class NetworkTemplate implements Parcelable {
* Check if matches Wi-Fi network template.
*/
private boolean matchesWifi(NetworkIdentity ident) {
- if (ident.mType == TYPE_WIFI) {
- return true;
+ switch (ident.mType) {
+ case TYPE_WIFI:
+ case TYPE_WIFI_P2P:
+ return true;
+ default:
+ return false;
}
- return false;
}
/**
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 8bdb669..dfdea38 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -195,7 +195,7 @@ public class TrafficStats {
// subtract starting values and return delta
final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
final NetworkStats profilingDelta = NetworkStats.subtract(
- profilingStop, sActiveProfilingStart, null);
+ profilingStop, sActiveProfilingStart, null, null);
sActiveProfilingStart = null;
return profilingDelta;
}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 0fb49bc..defe7aa 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Locale;
import java.util.RandomAccess;
import java.util.Set;
import libcore.net.UriCodec;
@@ -1716,6 +1717,38 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
return (!"false".equals(flag) && !"0".equals(flag));
}
+ /**
+ * Return a normalized representation of this Uri.
+ *
+ * <p>A normalized Uri has a lowercase scheme component.
+ * This aligns the Uri with Android best practices for
+ * intent filtering.
+ *
+ * <p>For example, "HTTP://www.android.com" becomes
+ * "http://www.android.com"
+ *
+ * <p>All URIs received from outside Android (such as user input,
+ * or external sources like Bluetooth, NFC, or the Internet) should
+ * be normalized before they are used to create an Intent.
+ *
+ * <p class="note">This method does <em>not</em> validate bad URI's,
+ * or 'fix' poorly formatted URI's - so do not use it for input validation.
+ * A Uri will always be returned, even if the Uri is badly formatted to
+ * begin with and a scheme component cannot be found.
+ *
+ * @return normalized Uri (never null)
+ * @see {@link android.content.Intent#setData}
+ * @see {@link #setNormalizedData}
+ */
+ public Uri normalize() {
+ String scheme = getScheme();
+ if (scheme == null) return this; // give up
+ String lowerScheme = scheme.toLowerCase(Locale.US);
+ if (scheme.equals(lowerScheme)) return this; // no change
+
+ return buildUpon().scheme(lowerScheme).build();
+ }
+
/** Identifies a null parcelled Uri. */
private static final int NULL_TYPE_ID = 0;
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 0b93ad0..d2afbb9 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -17,7 +17,6 @@
package android.nfc;
import android.app.PendingIntent;
-import android.content.ComponentName;
import android.content.IntentFilter;
import android.nfc.NdefMessage;
import android.nfc.Tag;
@@ -44,4 +43,6 @@ interface INfcAdapter
void setForegroundDispatch(in PendingIntent intent,
in IntentFilter[] filters, in TechListParcel techLists);
void setForegroundNdefPush(in NdefMessage msg, in INdefPushCallback callback);
+
+ void dispatch(in Tag tag, in NdefMessage message);
}
diff --git a/core/java/android/nfc/LlcpPacket.aidl b/core/java/android/nfc/LlcpPacket.aidl
deleted file mode 100644
index 80f424d..0000000
--- a/core/java/android/nfc/LlcpPacket.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-/**
- * @hide
- */
-parcelable LlcpPacket; \ No newline at end of file
diff --git a/core/java/android/nfc/LlcpPacket.java b/core/java/android/nfc/LlcpPacket.java
deleted file mode 100644
index 9919dc4..0000000
--- a/core/java/android/nfc/LlcpPacket.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.nfc;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Represents a LLCP packet received in a LLCP Connectionless communication;
- * @hide
- */
-public class LlcpPacket implements Parcelable {
-
- private final int mRemoteSap;
-
- private final byte[] mDataBuffer;
-
- /**
- * Creates a LlcpPacket to be sent to a remote Service Access Point number
- * (SAP)
- *
- * @param sap Remote Service Access Point number
- * @param data Data buffer
- */
- public LlcpPacket(int sap, byte[] data) {
- mRemoteSap = sap;
- mDataBuffer = data;
- }
-
- /**
- * Returns the remote Service Access Point number
- */
- public int getRemoteSap() {
- return mRemoteSap;
- }
-
- /**
- * Returns the data buffer
- */
- public byte[] getDataBuffer() {
- return mDataBuffer;
- }
-
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mRemoteSap);
- dest.writeInt(mDataBuffer.length);
- dest.writeByteArray(mDataBuffer);
- }
-
- public static final Parcelable.Creator<LlcpPacket> CREATOR = new Parcelable.Creator<LlcpPacket>() {
- public LlcpPacket createFromParcel(Parcel in) {
- // Remote SAP
- short sap = (short)in.readInt();
-
- // Data Buffer
- int dataLength = in.readInt();
- byte[] data = new byte[dataLength];
- in.readByteArray(data);
-
- return new LlcpPacket(sap, data);
- }
-
- public LlcpPacket[] newArray(int size) {
- return new LlcpPacket[size];
- }
- };
-} \ No newline at end of file
diff --git a/core/java/android/nfc/NdefMessage.java b/core/java/android/nfc/NdefMessage.java
index 38bc16d..c83144f 100644
--- a/core/java/android/nfc/NdefMessage.java
+++ b/core/java/android/nfc/NdefMessage.java
@@ -92,9 +92,7 @@ public final class NdefMessage implements Parcelable {
* @throws FormatException if the data cannot be parsed
*/
public NdefMessage(byte[] data) throws FormatException {
- if (data == null) {
- throw new NullPointerException("null data");
- }
+ if (data == null) throw new NullPointerException("data is null");
ByteBuffer buffer = ByteBuffer.wrap(data);
mRecords = NdefRecord.parse(buffer, false);
@@ -112,9 +110,8 @@ public final class NdefMessage implements Parcelable {
*/
public NdefMessage(NdefRecord record, NdefRecord ... records) {
// validate
- if (record == null) {
- throw new NullPointerException("record cannot be null");
- }
+ if (record == null) throw new NullPointerException("record cannot be null");
+
for (NdefRecord r : records) {
if (r == null) {
throw new NullPointerException("record cannot be null");
@@ -147,7 +144,12 @@ public final class NdefMessage implements Parcelable {
/**
* Get the NDEF Records inside this NDEF Message.<p>
- * An NDEF Message always has one or more NDEF Records.
+ * An {@link NdefMessage} always has one or more NDEF Records: so the
+ * following code to retrieve the first record is always safe
+ * (no need to check for null or array length >= 1):
+ * <pre>
+ * NdefRecord firstRecord = ndefMessage.getRecords()[0];
+ * </pre>
*
* @return array of one or more NDEF records.
*/
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index b4c488b..0e9e8f4 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -16,6 +16,7 @@
package android.nfc;
+import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,6 +26,7 @@ import java.nio.charset.Charsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
/**
* Represents an immutable NDEF Record.
@@ -305,9 +307,9 @@ public final class NdefRecord implements Parcelable {
* @return Android application NDEF record
*/
public static NdefRecord createApplicationRecord(String packageName) {
- if (packageName.length() == 0) {
- throw new IllegalArgumentException("empty package name");
- }
+ if (packageName == null) throw new NullPointerException("packageName is null");
+ if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
+
return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
packageName.getBytes(Charsets.UTF_8));
}
@@ -318,32 +320,27 @@ public final class NdefRecord implements Parcelable {
* Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
* and {@link #RTD_URI}. This is the most efficient encoding
* of a URI into NDEF.<p>
+ * The uri parameter will be normalized with
+ * {@link Uri#normalize} to set the scheme to lower case to
+ * follow Android best practices for intent filtering.
+ * However the unchecked exception
+ * {@link IllegalArgumentException} may be thrown if the uri
+ * parameter has serious problems, for example if it is empty, so always
+ * catch this exception if you are passing user-generated data into this
+ * method.<p>
+ *
* Reference specification: NFCForum-TS-RTD_URI_1.0
*
* @param uri URI to encode.
* @return an NDEF Record containing the URI
- * @throws IllegalArugmentException if a valid record cannot be created
+ * @throws IllegalArugmentException if the uri is empty or invalid
*/
public static NdefRecord createUri(Uri uri) {
- return createUri(uri.toString());
- }
+ if (uri == null) throw new NullPointerException("uri is null");
- /**
- * Create a new NDEF Record containing a URI.<p>
- * Use this method to encode a URI (or URL) into an NDEF Record.<p>
- * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
- * and {@link #RTD_URI}. This is the most efficient encoding
- * of a URI into NDEF.<p>
- * Reference specification: NFCForum-TS-RTD_URI_1.0
- *
- * @param uriString string URI to encode.
- * @return an NDEF Record containing the URI
- * @throws IllegalArugmentException if a valid record cannot be created
- */
- public static NdefRecord createUri(String uriString) {
- if (uriString.length() == 0) {
- throw new IllegalArgumentException("empty uriString");
- }
+ uri = uri.normalize();
+ String uriString = uri.toString();
+ if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty");
byte prefix = 0;
for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
@@ -361,28 +358,72 @@ public final class NdefRecord implements Parcelable {
}
/**
+ * Create a new NDEF Record containing a URI.<p>
+ * Use this method to encode a URI (or URL) into an NDEF Record.<p>
+ * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
+ * and {@link #RTD_URI}. This is the most efficient encoding
+ * of a URI into NDEF.<p>
+ * The uriString parameter will be normalized with
+ * {@link Uri#normalize} to set the scheme to lower case to
+ * follow Android best practices for intent filtering.
+ * However the unchecked exception
+ * {@link IllegalArgumentException} may be thrown if the uriString
+ * parameter has serious problems, for example if it is empty, so always
+ * catch this exception if you are passing user-generated data into this
+ * method.<p>
+ *
+ * Reference specification: NFCForum-TS-RTD_URI_1.0
+ *
+ * @param uriString string URI to encode.
+ * @return an NDEF Record containing the URI
+ * @throws IllegalArugmentException if the uriString is empty or invalid
+ */
+ public static NdefRecord createUri(String uriString) {
+ return createUri(Uri.parse(uriString));
+ }
+
+ /**
* Create a new NDEF Record containing MIME data.<p>
* Use this method to encode MIME-typed data into an NDEF Record,
* such as "text/plain", or "image/jpeg".<p>
- * Expects US-ASCII characters in mimeType. The encoding of the
- * mimeData depends on the mimeType.<p>
+ * The mimeType parameter will be normalized with
+ * {@link Intent#normalizeMimeType} to follow Android best
+ * practices for intent filtering, for example to force lower-case.
+ * However the unchecked exception
+ * {@link IllegalArgumentException} may be thrown
+ * if the mimeType parameter has serious problems,
+ * for example if it is empty, so always catch this
+ * exception if you are passing user-generated data into this method.
+ * <p>
* For efficiency, This method might not make an internal copy of the
* mimeData byte array, so take care not
- * to re-use the mimeData byte array while still using the returned
+ * to modify the mimeData byte array while still using the returned
* NdefRecord.
*
- * @param mimeType MIME type, expects US-ASCII characters only
+ * @param mimeType a valid MIME type
* @param mimeData MIME data as bytes
* @return an NDEF Record containing the MIME-typed data
- * @throws IllegalArugmentException if a valid record cannot be created
+ * @throws IllegalArugmentException if the mimeType is empty or invalid
+ *
*/
public static NdefRecord createMime(String mimeType, byte[] mimeData) {
- if (mimeType.length() == 0) {
- throw new IllegalArgumentException("empty mimeType");
+ if (mimeType == null) throw new NullPointerException("mimeType is null");
+
+ // We only do basic MIME type validation: trying to follow the
+ // RFCs strictly only ends in tears, since there are lots of MIME
+ // types in common use that are not strictly valid as per RFC rules
+ mimeType = Intent.normalizeMimeType(mimeType);
+ if (mimeType.length() == 0) throw new IllegalArgumentException("mimeType is empty");
+ int slashIndex = mimeType.indexOf('/');
+ if (slashIndex == 0) throw new IllegalArgumentException("mimeType must have major type");
+ if (slashIndex == mimeType.length() - 1) {
+ throw new IllegalArgumentException("mimeType must have minor type");
}
+ // missing '/' is allowed
- return new NdefRecord(TNF_MIME_MEDIA, mimeType.getBytes(Charsets.US_ASCII), null,
- mimeData);
+ // MIME RFCs suggest ASCII encoding for content-type
+ byte[] typeBytes = mimeType.getBytes(Charsets.US_ASCII);
+ return new NdefRecord(TNF_MIME_MEDIA, typeBytes, null, mimeData);
}
/**
@@ -391,32 +432,38 @@ public final class NdefRecord implements Parcelable {
* The data is typed by a domain name (usually your Android package name) and
* a domain-specific type. This data is packaged into a "NFC Forum External
* Type" NDEF Record.<p>
- * Both the domain and type used to construct an external record are case
- * insensitive, and this implementation will encode all characters to lower
- * case. Only a subset of ASCII characters are allowed for the domain
- * and type. There are no restrictions on the payload data.<p>
+ * NFC Forum requires that the domain and type used in an external record
+ * are treated as case insensitive, however Android intent filtering is
+ * always case sensitive. So this method will force the domain and type to
+ * lower-case before creating the NDEF Record.<p>
+ * The unchecked exception {@link IllegalArgumentException} will be thrown
+ * if the domain and type have serious problems, for example if either field
+ * is empty, so always catch this
+ * exception if you are passing user-generated data into this method.<p>
+ * There are no such restrictions on the payload data.<p>
* For efficiency, This method might not make an internal copy of the
* data byte array, so take care not
- * to re-use the data byte array while still using the returned
+ * to modify the data byte array while still using the returned
* NdefRecord.
*
* Reference specification: NFCForum-TS-RTD_1.0
* @param domain domain-name of issuing organization
* @param type domain-specific type of data
* @param data payload as bytes
- * @throws IllegalArugmentException if a valid record cannot be created
+ * @throws IllegalArugmentException if either domain or type are empty or invalid
*/
public static NdefRecord createExternal(String domain, String type, byte[] data) {
- if (domain.length() == 0 || type.length() == 0) {
- throw new IllegalArgumentException("empty domain or type");
- }
- byte[] byteDomain = domain.getBytes(Charsets.US_ASCII);
- ensureValidDomain(byteDomain);
- toLowerCase(byteDomain);
- byte[] byteType = type.getBytes(Charsets.US_ASCII);
- ensureValidWkt(byteType);
- toLowerCase(byteType);
+ if (domain == null) throw new NullPointerException("domain is null");
+ if (type == null) throw new NullPointerException("type is null");
+
+ domain = domain.trim().toLowerCase(Locale.US);
+ type = type.trim().toLowerCase(Locale.US);
+
+ if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
+ if (type.length() == 0) throw new IllegalArgumentException("type is empty");
+ byte[] byteDomain = domain.getBytes(Charsets.UTF_8);
+ byte[] byteType = type.getBytes(Charsets.UTF_8);
byte[] b = new byte[byteDomain.length + 1 + byteType.length];
System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
b[byteDomain.length] = ':';
@@ -574,51 +621,113 @@ public final class NdefRecord implements Parcelable {
}
/**
- * Helper to return the NdefRecord as a URI.
- * TODO: Consider making a member method instead of static
- * TODO: Consider more validation that this is a URI record
- * TODO: Make a public API
- * @hide
+ * Map this record to a MIME type, or return null if it cannot be mapped.<p>
+ * Currently this method considers all {@link #TNF_MIME_MEDIA} records to
+ * be MIME records, as well as some {@link #TNF_WELL_KNOWN} records such as
+ * {@link #RTD_TEXT}. If this is a MIME record then the MIME type as string
+ * is returned, otherwise null is returned.<p>
+ * This method does not perform validation that the MIME type is
+ * actually valid. It always attempts to
+ * return a string containing the type if this is a MIME record.<p>
+ * The returned MIME type will by normalized to lower-case using
+ * {@link Intent#normalizeMimeType}.<p>
+ * The MIME payload can be obtained using {@link #getPayload}.
+ *
+ * @return MIME type as a string, or null if this is not a MIME record
*/
- public static Uri parseWellKnownUriRecord(NdefRecord record) throws FormatException {
- byte[] payload = record.getPayload();
- if (payload.length < 2) {
- throw new FormatException("Payload is not a valid URI (missing prefix)");
+ public String toMimeType() {
+ switch (mTnf) {
+ case NdefRecord.TNF_WELL_KNOWN:
+ if (Arrays.equals(mType, NdefRecord.RTD_TEXT)) {
+ return "text/plain";
+ }
+ break;
+ case NdefRecord.TNF_MIME_MEDIA:
+ String mimeType = new String(mType, Charsets.US_ASCII);
+ return Intent.normalizeMimeType(mimeType);
}
+ return null;
+ }
- /*
- * payload[0] contains the URI Identifier Code, per the
- * NFC Forum "URI Record Type Definition" section 3.2.2.
- *
- * payload[1]...payload[payload.length - 1] contains the rest of
- * the URI.
- */
- int prefixIndex = (payload[0] & 0xff);
- if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
- throw new FormatException("Payload is not a valid URI (invalid prefix)");
+ /**
+ * Map this record to a URI, or return null if it cannot be mapped.<p>
+ * Currently this method considers the following to be URI records:
+ * <ul>
+ * <li>{@link #TNF_ABSOLUTE_URI} records.</li>
+ * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_URI}.</li>
+ * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_SMART_POSTER}
+ * and containing a URI record in the NDEF message nested in the payload.
+ * </li>
+ * <li>{@link #TNF_EXTERNAL_TYPE} records.</li>
+ * </ul>
+ * If this is not a URI record by the above rules, then null is returned.<p>
+ * This method does not perform validation that the URI is
+ * actually valid: it always attempts to create and return a URI if
+ * this record appears to be a URI record by the above rules.<p>
+ * The returned URI will be normalized to have a lower case scheme
+ * using {@link Uri#normalize}.<p>
+ *
+ * @return URI, or null if this is not a URI record
+ */
+ public Uri toUri() {
+ return toUri(false);
+ }
+
+ private Uri toUri(boolean inSmartPoster) {
+ switch (mTnf) {
+ case TNF_WELL_KNOWN:
+ if (Arrays.equals(mType, RTD_SMART_POSTER) && !inSmartPoster) {
+ try {
+ // check payload for a nested NDEF Message containing a URI
+ NdefMessage nestedMessage = new NdefMessage(mPayload);
+ for (NdefRecord nestedRecord : nestedMessage.getRecords()) {
+ Uri uri = nestedRecord.toUri(true);
+ if (uri != null) {
+ return uri;
+ }
+ }
+ } catch (FormatException e) { }
+ } else if (Arrays.equals(mType, RTD_URI)) {
+ return parseWktUri().normalize();
+ }
+ break;
+
+ case TNF_ABSOLUTE_URI:
+ Uri uri = Uri.parse(new String(mType, Charsets.UTF_8));
+ return uri.normalize();
+
+ case TNF_EXTERNAL_TYPE:
+ if (inSmartPoster) {
+ break;
+ }
+ return Uri.parse("vnd.android.nfc://ext/" + new String(mType, Charsets.US_ASCII));
}
- String prefix = URI_PREFIX_MAP[prefixIndex];
- byte[] fullUri = concat(prefix.getBytes(Charsets.UTF_8),
- Arrays.copyOfRange(payload, 1, payload.length));
- return Uri.parse(new String(fullUri, Charsets.UTF_8));
+ return null;
}
- private static byte[] concat(byte[]... arrays) {
- int length = 0;
- for (byte[] array : arrays) {
- length += array.length;
+ /**
+ * Return complete URI of {@link #TNF_WELL_KNOWN}, {@link #RTD_URI} records.
+ * @return complete URI, or null if invalid
+ */
+ private Uri parseWktUri() {
+ if (mPayload.length < 2) {
+ return null;
}
- byte[] result = new byte[length];
- int pos = 0;
- for (byte[] array : arrays) {
- System.arraycopy(array, 0, result, pos, array.length);
- pos += array.length;
+
+ // payload[0] contains the URI Identifier Code, as per
+ // NFC Forum "URI Record Type Definition" section 3.2.2.
+ int prefixIndex = (mPayload[0] & (byte)0xFF);
+ if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
+ return null;
}
- return result;
+ String prefix = URI_PREFIX_MAP[prefixIndex];
+ String suffix = new String(Arrays.copyOfRange(mPayload, 1, mPayload.length),
+ Charsets.UTF_8);
+ return Uri.parse(prefix + suffix);
}
/**
- * Main parsing method.<p>
+ * Main record parsing method.<p>
* Expects NdefMessage to begin immediately, allows trailing data.<p>
* Currently has strict validation of all fields as per NDEF 1.0
* specification section 2.5. We will attempt to keep this as strict as
@@ -902,42 +1011,4 @@ public final class NdefRecord implements Parcelable {
}
return s;
}
-
- /** Ensure valid 'DNS-char' as per RFC2234 */
- private static void ensureValidDomain(byte[] bs) {
- for (int i = 0; i < bs.length; i++) {
- byte b = bs[i];
- if ((b >= 'A' && b <= 'Z') ||
- (b >= 'a' && b <= 'z') ||
- (b >= '0' && b <= '9') ||
- b == '.' || b == '-') {
- continue;
- }
- throw new IllegalArgumentException("invalid character in domain");
- }
- }
-
- /** Ensure valid 'WKT-char' as per RFC2234 */
- private static void ensureValidWkt(byte[] bs) {
- for (int i = 0; i < bs.length; i++) {
- byte b = bs[i];
- if ((b >= 'A' && b <= 'Z') ||
- (b >= 'a' && b <= 'z') ||
- (b >= '0' && b <= '9') ||
- b == '(' || b == ')' || b == '+' || b == ',' || b == '-' ||
- b == ':' || b == '=' || b == '@' || b == ';' || b == '$' ||
- b == '_' || b == '!' || b == '*' || b == '\'' || b == '.') {
- continue;
- }
- throw new IllegalArgumentException("invalid character in type");
- }
- }
-
- private static void toLowerCase(byte[] b) {
- for (int i = 0; i < b.length; i++) {
- if (b[i] >= 'A' && b[i] <= 'Z') {
- b[i] += 0x20;
- }
- }
- }
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 53a0341..224a8bc 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -66,6 +66,9 @@ public final class NfcAdapter {
* <p>If the tag has an NDEF payload this intent is started before
* {@link #ACTION_TECH_DISCOVERED}. If any activities respond to this intent neither
* {@link #ACTION_TECH_DISCOVERED} or {@link #ACTION_TAG_DISCOVERED} will be started.
+ *
+ * <p>The MIME type or data URI of this intent are normalized before dispatch -
+ * so that MIME, URI scheme and URI host are always lower-case.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
@@ -151,9 +154,13 @@ public final class NfcAdapter {
public static final String EXTRA_TAG = "android.nfc.extra.TAG";
/**
- * Optional extra containing an array of {@link NdefMessage} present on the discovered tag for
- * the {@link #ACTION_NDEF_DISCOVERED}, {@link #ACTION_TECH_DISCOVERED}, and
- * {@link #ACTION_TAG_DISCOVERED} intents.
+ * Extra containing an array of {@link NdefMessage} present on the discovered tag.<p>
+ * This extra is mandatory for {@link #ACTION_NDEF_DISCOVERED} intents,
+ * and optional for {@link #ACTION_TECH_DISCOVERED}, and
+ * {@link #ACTION_TAG_DISCOVERED} intents.<p>
+ * When this extra is present there will always be at least one
+ * {@link NdefMessage} element. Most NDEF tags have only one NDEF message,
+ * but we use an array for future compatibility.
*/
public static final String EXTRA_NDEF_MESSAGES = "android.nfc.extra.NDEF_MESSAGES";
@@ -386,10 +393,10 @@ public final class NfcAdapter {
*/
@Deprecated
public static NfcAdapter getDefaultAdapter() {
- // introduce in API version 9 (GB 2.3)
+ // introduced in API version 9 (GB 2.3)
// deprecated in API version 10 (GB 2.3.3)
// removed from public API in version 16 (ICS MR2)
- // will need to maintain this as a hidden API for a while longer...
+ // should maintain as a hidden API for binary compatibility for a little longer
Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
"NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
@@ -803,6 +810,7 @@ public final class NfcAdapter {
* @throws IllegalStateException if the Activity has already been paused
* @deprecated use {@link #setNdefPushMessage} instead
*/
+ @Deprecated
public void disableForegroundNdefPush(Activity activity) {
if (activity == null) {
throw new NullPointerException();
@@ -875,6 +883,24 @@ public final class NfcAdapter {
}
/**
+ * Inject a mock NFC tag.<p>
+ * Used for testing purposes.
+ * <p class="note">Requires the
+ * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission.
+ * @hide
+ */
+ public void dispatch(Tag tag, NdefMessage message) {
+ if (tag == null) {
+ throw new NullPointerException("tag cannot be null");
+ }
+ try {
+ sService.dispatch(tag, message);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
+ /**
* @hide
*/
public INfcAdapterExtras getNfcAdapterExtrasInterface() {
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 88fea91..c106092 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -167,6 +167,8 @@ public class Build {
* medium density normal size screens unless otherwise indicated).
* They can still explicitly specify screen support either way with the
* supports-screens manifest tag.
+ * <li> {@link android.widget.TabHost} will use the new dark tab
+ * background design.
* </ul>
*/
public static final int DONUT = 4;
@@ -208,6 +210,13 @@ public class Build {
/**
* November 2010: Android 2.3
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li> The application's notification icons will be shown on the new
+ * dark status bar background, so must be visible in this situation.
+ * </ul>
*/
public static final int GINGERBREAD = 9;
@@ -224,14 +233,34 @@ public class Build {
* <ul>
* <li> The default theme for applications is now dark holographic:
* {@link android.R.style#Theme_Holo}.
+ * <li> On large screen devices that do not have a physical menu
+ * button, the soft (compatibility) menu is disabled.
* <li> The activity lifecycle has changed slightly as per
* {@link android.app.Activity}.
+ * <li> An application will crash if it does not call through
+ * to the super implementation of its
+ * {@link android.app.Activity#onPause Activity.onPause()} method.
* <li> When an application requires a permission to access one of
* its components (activity, receiver, service, provider), this
* permission is no longer enforced when the application wants to
* access its own component. This means it can require a permission
* on a component that it does not itself hold and still access that
* component.
+ * <li> {@link android.content.Context#getSharedPreferences
+ * Context.getSharedPreferences()} will not automatically reload
+ * the preferences if they have changed on storage, unless
+ * {@link android.content.Context#MODE_MULTI_PROCESS} is used.
+ * <li> {@link android.view.ViewGroup#setMotionEventSplittingEnabled}
+ * will default to true.
+ * <li> {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH}
+ * is enabled by default on windows.
+ * <li> {@link android.widget.PopupWindow#isSplitTouchEnabled()
+ * PopupWindow.isSplitTouchEnabled()} will return true by default.
+ * <li> {@link android.widget.GridView} and {@link android.widget.ListView}
+ * will use {@link android.view.View#setActivated View.setActivated}
+ * for selected items if they do not implement {@link android.widget.Checkable}.
+ * <li> {@link android.widget.Scroller} will be constructed with
+ * "flywheel" behavior enabled by default.
* </ul>
*/
public static final int HONEYCOMB = 11;
@@ -266,13 +295,26 @@ public class Build {
* preferred over the older screen size buckets and for older devices
* the appropriate buckets will be inferred from them.</p>
*
- * <p>New {@link android.content.pm.PackageManager#FEATURE_SCREEN_PORTRAIT}
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li><p>New {@link android.content.pm.PackageManager#FEATURE_SCREEN_PORTRAIT}
* and {@link android.content.pm.PackageManager#FEATURE_SCREEN_LANDSCAPE}
- * features are introduced in this release. Applications that target
+ * features were introduced in this release. Applications that target
* previous platform versions are assumed to require both portrait and
* landscape support in the device; when targeting Honeycomb MR1 or
* greater the application is responsible for specifying any specific
* orientation it requires.</p>
+ * <li><p>{@link android.os.AsyncTask} will use the serial executor
+ * by default when calling {@link android.os.AsyncTask#execute}.</p>
+ * <li><p>{@link android.content.pm.ActivityInfo#configChanges
+ * ActivityInfo.configChanges} will have the
+ * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_SIZE} and
+ * {@link android.content.pm.ActivityInfo#CONFIG_SMALLEST_SCREEN_SIZE}
+ * bits set; these need to be cleared for older applications because
+ * some developers have done absolute comparisons against this value
+ * instead of correctly masking the bits they are interested in.
+ * </ul>
*/
public static final int HONEYCOMB_MR2 = 13;
@@ -306,14 +348,31 @@ public class Build {
* <li> The fadingEdge attribute on views will be ignored (fading edges is no
* longer a standard part of the UI). A new requiresFadingEdge attribute allows
* applications to still force fading edges on for special cases.
+ * <li> {@link android.content.Context#bindService Context.bindService()}
+ * will not automatically add in {@link android.content.Context#BIND_WAIVE_PRIORITY}.
+ * <li> App Widgets will have standard padding automatically added around
+ * them, rather than relying on the padding being baked into the widget itself.
+ * <li> An exception will be thrown if you try to change the type of a
+ * window after it has been added to the window manager. Previously this
+ * would result in random incorrect behavior.
+ * <li> {@link android.view.animation.AnimationSet} will parse out
+ * the duration, fillBefore, fillAfter, repeatMode, and startOffset
+ * XML attributes that are defined.
+ * <li> {@link android.app.ActionBar#setHomeButtonEnabled
+ * ActionBar.setHomeButtonEnabled()} is false by default.
* </ul>
*/
public static final int ICE_CREAM_SANDWICH = 14;
/**
- * Android 4.0.3.
+ * December 2011: Android 4.0.3.
*/
public static final int ICE_CREAM_SANDWICH_MR1 = 15;
+
+ /**
+ * Next up on Android!
+ */
+ public static final int JELLY_BEAN = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 9a53d76..270e9be 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -45,4 +45,5 @@ interface IPowerManager
// sets the brightness of the backlights (screen, keyboard, button) 0-255
void setBacklightBrightness(int brightness);
void setAttentionLight(boolean on, int color);
+ void setAutoBrightnessAdjustment(float adj);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0202c47..ef8cb16 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1401,6 +1401,12 @@ public final class Settings {
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
/**
+ * Adjustment to auto-brightness to make it generally more (>0.0 <1.0)
+ * or less (<0.0 >-1.0) bright.
+ */
+ public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj";
+
+ /**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
*/
public static final int SCREEN_BRIGHTNESS_MODE_MANUAL = 0;
@@ -1927,6 +1933,7 @@ public final class Settings {
SCREEN_OFF_TIMEOUT,
SCREEN_BRIGHTNESS,
SCREEN_BRIGHTNESS_MODE,
+ SCREEN_AUTO_BRIGHTNESS_ADJ,
VIBRATE_ON,
MODE_RINGER,
MODE_RINGER_STREAMS_AFFECTED,
@@ -4104,17 +4111,38 @@ public final class Settings {
/** {@hide} */
public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval";
/** {@hide} */
- public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold";
+ public static final String NETSTATS_TIME_CACHE_MAX_AGE = "netstats_time_cache_max_age";
+ /** {@hide} */
+ public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes";
/** {@hide} */
- public static final String NETSTATS_NETWORK_BUCKET_DURATION = "netstats_network_bucket_duration";
+ public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled";
+
+ /** {@hide} */
+ public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration";
/** {@hide} */
- public static final String NETSTATS_NETWORK_MAX_HISTORY = "netstats_network_max_history";
+ public static final String NETSTATS_DEV_PERSIST_BYTES = "netstats_dev_persist_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_DEV_ROTATE_AGE = "netstats_dev_rotate_age";
+ /** {@hide} */
+ public static final String NETSTATS_DEV_DELETE_AGE = "netstats_dev_delete_age";
+
/** {@hide} */
public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
/** {@hide} */
- public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history";
+ public static final String NETSTATS_UID_PERSIST_BYTES = "netstats_uid_persist_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_UID_ROTATE_AGE = "netstats_uid_rotate_age";
+ /** {@hide} */
+ public static final String NETSTATS_UID_DELETE_AGE = "netstats_uid_delete_age";
+
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_BUCKET_DURATION = "netstats_uid_tag_bucket_duration";
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_PERSIST_BYTES = "netstats_uid_tag_persist_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_ROTATE_AGE = "netstats_uid_tag_rotate_age";
/** {@hide} */
- public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history";
+ public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age";
/** Preferred NTP server. {@hide} */
public static final String NTP_SERVER = "ntp_server";
diff --git a/core/java/android/provider/UserDictionary.java b/core/java/android/provider/UserDictionary.java
index 5a7ef85..a9b106a 100644
--- a/core/java/android/provider/UserDictionary.java
+++ b/core/java/android/provider/UserDictionary.java
@@ -40,6 +40,9 @@ public class UserDictionary {
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY);
+ private static final int FREQUENCY_MIN = 0;
+ private static final int FREQUENCY_MAX = 255;
+
/**
* Contains the user defined words.
*/
@@ -87,12 +90,24 @@ public class UserDictionary {
*/
public static final String APP_ID = "appid";
- /** The locale type to specify that the word is common to all locales. */
+ /**
+ * An optional shortcut for this word. When the shortcut is typed, supporting IMEs should
+ * suggest the word in this row as an alternate spelling too.
+ */
+ public static final String SHORTCUT = "shortcut";
+
+ /**
+ * @deprecated Use {@link #addWord(Context, String, int, String, Locale)}.
+ */
+ @Deprecated
public static final int LOCALE_TYPE_ALL = 0;
-
- /** The locale type to specify that the word is for the current locale. */
+
+ /**
+ * @deprecated Use {@link #addWord(Context, String, int, String, Locale)}.
+ */
+ @Deprecated
public static final int LOCALE_TYPE_CURRENT = 1;
-
+
/**
* Sort by descending order of frequency.
*/
@@ -100,35 +115,65 @@ public class UserDictionary {
/** Adds a word to the dictionary, with the given frequency and the specified
* specified locale type.
+ *
+ * @deprecated Please use
+ * {@link #addWord(Context, String, int, String, Locale)} instead.
+ *
* @param context the current application context
* @param word the word to add to the dictionary. This should not be null or
* empty.
* @param localeType the locale type for this word. It should be one of
* {@link #LOCALE_TYPE_ALL} or {@link #LOCALE_TYPE_CURRENT}.
*/
- public static void addWord(Context context, String word,
+ @Deprecated
+ public static void addWord(Context context, String word,
int frequency, int localeType) {
- final ContentResolver resolver = context.getContentResolver();
- if (TextUtils.isEmpty(word) || localeType < 0 || localeType > 1) {
+ if (localeType != LOCALE_TYPE_ALL && localeType != LOCALE_TYPE_CURRENT) {
return;
}
-
- if (frequency < 0) frequency = 0;
- if (frequency > 255) frequency = 255;
- String locale = null;
+ final Locale locale;
- // TODO: Verify if this is the best way to get the current locale
if (localeType == LOCALE_TYPE_CURRENT) {
- locale = Locale.getDefault().toString();
+ locale = Locale.getDefault();
+ } else {
+ locale = null;
}
- ContentValues values = new ContentValues(4);
+
+ addWord(context, word, frequency, null, locale);
+ }
+
+ /** Adds a word to the dictionary, with the given frequency and the specified
+ * locale type.
+ *
+ * @param context the current application context
+ * @param word the word to add to the dictionary. This should not be null or
+ * empty.
+ * @param shortcut optional shortcut spelling for this word. When the shortcut
+ * is typed, the word may be suggested by applications that support it. May be null.
+ * @param locale the locale to insert the word for, or null to insert the word
+ * for all locales.
+ */
+ public static void addWord(Context context, String word,
+ int frequency, String shortcut, Locale locale) {
+ final ContentResolver resolver = context.getContentResolver();
+
+ if (TextUtils.isEmpty(word)) {
+ return;
+ }
+
+ if (frequency < FREQUENCY_MIN) frequency = FREQUENCY_MIN;
+ if (frequency > FREQUENCY_MAX) frequency = FREQUENCY_MAX;
+
+ final int COLUMN_COUNT = 5;
+ ContentValues values = new ContentValues(COLUMN_COUNT);
values.put(WORD, word);
values.put(FREQUENCY, frequency);
- values.put(LOCALE, locale);
+ values.put(LOCALE, null == locale ? null : locale.toString());
values.put(APP_ID, 0); // TODO: Get App UID
+ values.put(SHORTCUT, shortcut);
Uri result = resolver.insert(CONTENT_URI, values);
// It's ok if the insert doesn't succeed because the word
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index c184c11..a52e2ba 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -109,6 +109,9 @@ class MeasuredText {
for (int i = 0; i < spans.length; i++) {
int startInPara = spanned.getSpanStart(spans[i]) - start;
int endInPara = spanned.getSpanEnd(spans[i]) - start;
+ // The span interval may be larger and must be restricted to [start, end[
+ if (startInPara < 0) startInPara = 0;
+ if (endInPara > len) endInPara = len;
for (int j = startInPara; j < endInPara; j++) {
mChars[j] = '\uFFFC';
}
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 4ec4bc4..30bb447 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -280,8 +280,6 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
if (isSelecting(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
Selection.extendSelection(buffer, offset);
- } else if (!widget.shouldIgnoreActionUpEvent()) {
- Selection.setSelection(buffer, offset);
}
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 519b980..a43d36c 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -57,6 +57,13 @@ public class DisplayMetrics {
public static final int DENSITY_XHIGH = 320;
/**
+ * Standard quantized DPI for extra-extra-high-density screens. Applications
+ * should not generally worry about this density; relying on XHIGH graphics
+ * being scaled up to it should be sufficient for almost all cases.
+ */
+ public static final int DENSITY_XXHIGH = 480;
+
+ /**
* The reference density used throughout the system.
*/
public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
new file mode 100644
index 0000000..641d1b4
--- /dev/null
+++ b/core/java/android/util/LocalLog.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.text.format.Time;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * @hide
+ */
+public final class LocalLog {
+
+ private LinkedList<String> mLog;
+ private int mMaxLines;
+ private Time mNow;
+
+ public LocalLog(int maxLines) {
+ mLog = new LinkedList<String>();
+ mMaxLines = maxLines;
+ mNow = new Time();
+ }
+
+ public synchronized void log(String msg) {
+ if (mMaxLines > 0) {
+ mNow.setToNow();
+ mLog.add(mNow.format("%H:%M:%S") + " - " + msg);
+ while (mLog.size() > mMaxLines) mLog.remove();
+ }
+ }
+
+ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ Iterator<String> itr = mLog.listIterator(0);
+ while (itr.hasNext()) {
+ pw.println(itr.next());
+ }
+ }
+}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 761a788..c08a402 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -22,6 +22,7 @@ import android.graphics.ColorFilter;
import android.graphics.DrawFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Picture;
import android.graphics.PorterDuff;
@@ -546,6 +547,7 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nSetMatrix(int renderer, int matrix);
+ @SuppressWarnings("deprecation")
@Override
public void getMatrix(Matrix matrix) {
nGetMatrix(mRenderer, matrix.native_instance);
@@ -658,8 +660,17 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void setDrawFilter(DrawFilter filter) {
mFilter = filter;
+ if (filter == null) {
+ nResetPaintFilter(mRenderer);
+ } else if (filter instanceof PaintFlagsDrawFilter) {
+ PaintFlagsDrawFilter flagsFilter = (PaintFlagsDrawFilter) filter;
+ nSetupPaintFilter(mRenderer, flagsFilter.clearBits, flagsFilter.setBits);
+ }
}
+ private static native void nResetPaintFilter(int renderer);
+ private static native void nSetupPaintFilter(int renderer, int clearBits, int setBits);
+
@Override
public DrawFilter getDrawFilter() {
return mFilter;
@@ -908,17 +919,42 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPicture(Picture picture) {
- throw new UnsupportedOperationException();
+ if (picture.createdFromStream) {
+ return;
+ }
+
+ picture.endRecording();
+ // TODO: Implement rendering
}
@Override
public void drawPicture(Picture picture, Rect dst) {
- throw new UnsupportedOperationException();
+ if (picture.createdFromStream) {
+ return;
+ }
+
+ save();
+ translate(dst.left, dst.top);
+ if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+ scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight());
+ }
+ drawPicture(picture);
+ restore();
}
@Override
public void drawPicture(Picture picture, RectF dst) {
- throw new UnsupportedOperationException();
+ if (picture.createdFromStream) {
+ return;
+ }
+
+ save();
+ translate(dst.left, dst.top);
+ if (picture.getWidth() > 0 && picture.getHeight() > 0) {
+ scale(dst.width() / picture.getWidth(), dst.height() / picture.getHeight());
+ }
+ drawPicture(picture);
+ restore();
}
@Override
@@ -943,6 +979,7 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawPoints(int renderer, float[] points,
int offset, int count, int paint);
+ @SuppressWarnings("deprecation")
@Override
public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
if (index < 0 || index + count > text.length || count * 2 > pos.length) {
@@ -960,6 +997,7 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawPosText(int renderer, char[] text, int index, int count,
float[] pos, int paint);
+ @SuppressWarnings("deprecation")
@Override
public void drawPosText(String text, float[] pos, Paint paint) {
if (text.length() * 2 > pos.length) {
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 4592ae6..1c9cbbf 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -238,6 +238,15 @@ public abstract class HardwareRenderer {
private static native void nSetupShadersDiskCache(String cacheFile);
/**
+ * Notifies EGL that the frame is about to be rendered.
+ */
+ private static void beginFrame() {
+ nBeginFrame();
+ }
+
+ private static native void nBeginFrame();
+
+ /**
* Interface used to receive callbacks whenever a view is drawn by
* a hardware renderer instance.
*/
@@ -808,6 +817,7 @@ public abstract class HardwareRenderer {
}
void onPreDraw(Rect dirty) {
+
}
void onPostDraw() {
@@ -832,6 +842,8 @@ public abstract class HardwareRenderer {
dirty = null;
}
+ beginFrame();
+
onPreDraw(dirty);
HardwareCanvas canvas = mCanvas;
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 93a9d50..c54d09e 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -229,4 +229,9 @@ interface IWindowManager
* Device has a software navigation bar (separate from the status bar).
*/
boolean hasNavigationBar();
+
+ /**
+ * Lock the device immediately.
+ */
+ void lockNow();
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a9d6cdf..8cac57d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7996,84 +7996,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * @hide
- */
- public void setFastTranslationX(float x) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- info.mTranslationX = x;
- info.mMatrixDirty = true;
- }
-
- /**
- * @hide
- */
- public void setFastTranslationY(float y) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- info.mTranslationY = y;
- info.mMatrixDirty = true;
- }
-
- /**
- * @hide
- */
- public void setFastX(float x) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- info.mTranslationX = x - mLeft;
- info.mMatrixDirty = true;
- }
-
- /**
- * @hide
- */
- public void setFastY(float y) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- info.mTranslationY = y - mTop;
- info.mMatrixDirty = true;
- }
-
- /**
- * @hide
- */
- public void setFastScaleX(float x) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- info.mScaleX = x;
- info.mMatrixDirty = true;
- }
-
- /**
- * @hide
- */
- public void setFastScaleY(float y) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- info.mScaleY = y;
- info.mMatrixDirty = true;
- }
-
- /**
- * @hide
- */
- public void setFastAlpha(float alpha) {
- ensureTransformationInfo();
- mTransformationInfo.mAlpha = alpha;
- }
-
- /**
- * @hide
- */
- public void setFastRotationY(float y) {
- ensureTransformationInfo();
- final TransformationInfo info = mTransformationInfo;
- info.mRotationY = y;
- info.mMatrixDirty = true;
- }
-
- /**
* Hit rectangle in parent's coordinates
*
* @param outRect The hit rectangle of the view.
@@ -8650,37 +8572,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * @hide
- */
- public void fastInvalidate() {
- if (skipInvalidate()) {
- return;
- }
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
- (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID ||
- (mPrivateFlags & INVALIDATED) != INVALIDATED) {
- if (mParent instanceof View) {
- ((View) mParent).mPrivateFlags |= INVALIDATED;
- }
- mPrivateFlags &= ~DRAWN;
- mPrivateFlags |= DIRTY;
- mPrivateFlags |= INVALIDATED;
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
- if (mParent != null && mAttachInfo != null) {
- if (mAttachInfo.mHardwareAccelerated) {
- mParent.invalidateChild(this, null);
- } else {
- final Rect r = mAttachInfo.mTmpInvalRect;
- r.set(0, 0, mRight - mLeft, mBottom - mTop);
- // Don't call invalidate -- we don't want to internally scroll
- // our own bounds
- mParent.invalidateChild(this, r);
- }
- }
- }
- }
-
- /**
* Used to indicate that the parent of this view should clear its caches. This functionality
* is used to force the parent to rebuild its display list (when hardware-accelerated),
* which is necessary when various parent-managed properties of the view change, such as
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index c53fc6b..7fd3389 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -185,7 +185,8 @@ public final class ViewTreeObserver {
mTouchableInsets = TOUCHABLE_INSETS_FRAME;
}
- @Override public boolean equals(Object o) {
+ @Override
+ public boolean equals(Object o) {
try {
if (o == null) {
return false;
@@ -357,10 +358,26 @@ public final class ViewTreeObserver {
* @param victim The callback to remove
*
* @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @deprecated Use #removeOnGlobalLayoutListener instead
*
* @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
*/
+ @Deprecated
public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) {
+ removeOnGlobalLayoutListener(victim);
+ }
+
+ /**
+ * Remove a previously installed global layout callback
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener)
+ */
+ public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) {
checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
return;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 994565a..6ec2e8d 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1029,6 +1029,11 @@ public interface WindowManagerPolicy {
public boolean hasNavigationBar();
/**
+ * Lock the device now.
+ */
+ public void lockNow();
+
+ /**
* Print the WindowManagerPolicy's state into the given stream.
*
* @param prefix Text to print at the front of each line.
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index c3c74a7..c28b220 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -21,6 +21,7 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.util.FloatMath;
import android.util.Log;
import android.util.Slog;
@@ -48,6 +49,8 @@ public abstract class WindowOrientationListener {
private static final boolean DEBUG = false;
private static final boolean localLOGV = DEBUG || false;
+ private static final boolean USE_GRAVITY_SENSOR = false;
+
private SensorManager mSensorManager;
private boolean mEnabled;
private int mRate;
@@ -79,7 +82,8 @@ public abstract class WindowOrientationListener {
private WindowOrientationListener(Context context, int rate) {
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
mRate = rate;
- mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
+ ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
if (mSensor != null) {
// Create listener only if sensors do exist
mSensorEventListener = new SensorEventListenerImpl(this);
@@ -179,7 +183,7 @@ public abstract class WindowOrientationListener {
* cartesian space because the orientation calculations are sensitive to the
* absolute magnitude of the acceleration. In particular, there are singularities
* in the calculation as the magnitude approaches 0. By performing the low-pass
- * filtering early, we can eliminate high-frequency impulses systematically.
+ * filtering early, we can eliminate most spurious high-frequency impulses due to noise.
*
* - Convert the acceleromter vector from cartesian to spherical coordinates.
* Since we're dealing with rotation of the device, this is the sensible coordinate
@@ -204,11 +208,17 @@ public abstract class WindowOrientationListener {
* new orientation proposal.
*
* Details are explained inline.
+ *
+ * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
+ * signal processing background.
*/
static final class SensorEventListenerImpl implements SensorEventListener {
// We work with all angles in degrees in this class.
private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
+ // Number of nanoseconds per millisecond.
+ private static final long NANOS_PER_MS = 1000000;
+
// Indices into SensorEvent.values for the accelerometer sensor.
private static final int ACCELEROMETER_DATA_X = 0;
private static final int ACCELEROMETER_DATA_Y = 1;
@@ -216,38 +226,41 @@ public abstract class WindowOrientationListener {
private final WindowOrientationListener mOrientationListener;
- /* State for first order low-pass filtering of accelerometer data.
- * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
- * signal processing background.
- */
-
- private long mLastTimestamp = Long.MAX_VALUE; // in nanoseconds
- private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
-
- // The current proposal. We wait for the proposal to be stable for a
- // certain amount of time before accepting it.
- //
- // The basic idea is to ignore intermediate poses of the device while the
- // user is picking up, putting down or turning the device.
- private int mProposalRotation;
- private long mProposalAgeMS;
-
- // A historical trace of tilt and orientation angles. Used to determine whether
- // the device posture has settled down.
- private static final int HISTORY_SIZE = 20;
- private int mHistoryIndex; // index of most recent sample
- private int mHistoryLength; // length of historical trace
- private final long[] mHistoryTimestampMS = new long[HISTORY_SIZE];
- private final float[] mHistoryMagnitudes = new float[HISTORY_SIZE];
- private final int[] mHistoryTiltAngles = new int[HISTORY_SIZE];
- private final int[] mHistoryOrientationAngles = new int[HISTORY_SIZE];
+ // The minimum amount of time that a predicted rotation must be stable before it
+ // is accepted as a valid rotation proposal. This value can be quite small because
+ // the low-pass filter already suppresses most of the noise so we're really just
+ // looking for quick confirmation that the last few samples are in agreement as to
+ // the desired orientation.
+ private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS;
+
+ // The minimum amount of time that must have elapsed since the device last exited
+ // the flat state (time since it was picked up) before the proposed rotation
+ // can change.
+ private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS;
+
+ // The mininum amount of time that must have elapsed since the device stopped
+ // swinging (time since device appeared to be in the process of being put down
+ // or put away into a pocket) before the proposed rotation can change.
+ private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS;
+
+ // If the tilt angle remains greater than the specified angle for a minimum of
+ // the specified time, then the device is deemed to be lying flat
+ // (just chillin' on a table).
+ private static final float FLAT_ANGLE = 75;
+ private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS;
+
+ // If the tilt angle has increased by at least delta degrees within the specified amount
+ // of time, then the device is deemed to be swinging away from the user
+ // down towards flat (tilt = 90).
+ private static final float SWING_AWAY_ANGLE_DELTA = 20;
+ private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS;
// The maximum sample inter-arrival time in milliseconds.
// If the acceleration samples are further apart than this amount in time, we reset the
// state of the low-pass filter and orientation properties. This helps to handle
// boundary conditions when the device is turned on, wakes from suspend or there is
// a significant gap in samples.
- private static final float MAX_FILTER_DELTA_TIME_MS = 1000;
+ private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS;
// The acceleration filter time constant.
//
@@ -267,8 +280,10 @@ public abstract class WindowOrientationListener {
//
// Filtering adds latency proportional the time constant (inversely proportional
// to the cutoff frequency) so we don't want to make the time constant too
- // large or we can lose responsiveness.
- private static final float FILTER_TIME_CONSTANT_MS = 100.0f;
+ // large or we can lose responsiveness. Likewise we don't want to make it too
+ // small or we do a poor job suppressing acceleration spikes.
+ // Empirically, 100ms seems to be too small and 500ms is too large.
+ private static final float FILTER_TIME_CONSTANT_MS = 200.0f;
/* State for orientation detection. */
@@ -286,9 +301,9 @@ public abstract class WindowOrientationListener {
//
// In both cases, we postpone choosing an orientation.
private static final float MIN_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY * 0.5f;
+ SensorManager.STANDARD_GRAVITY * 0.3f;
private static final float MAX_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY * 1.5f;
+ SensorManager.STANDARD_GRAVITY * 1.25f;
// Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
// when screen is facing the sky or ground), we completely ignore orientation data.
@@ -306,10 +321,10 @@ public abstract class WindowOrientationListener {
// The ideal tilt angle is 0 (when the device is vertical) so the limits establish
// how close to vertical the device must be in order to change orientation.
private static final int[][] TILT_TOLERANCE = new int[][] {
- /* ROTATION_0 */ { -20, 70 },
- /* ROTATION_90 */ { -20, 60 },
- /* ROTATION_180 */ { -20, 50 },
- /* ROTATION_270 */ { -20, 60 }
+ /* ROTATION_0 */ { -25, 70 },
+ /* ROTATION_90 */ { -25, 65 },
+ /* ROTATION_180 */ { -25, 60 },
+ /* ROTATION_270 */ { -25, 65 }
};
// The gap angle in degrees between adjacent orientation angles for hysteresis.
@@ -319,29 +334,38 @@ public abstract class WindowOrientationListener {
// orientation.
private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45;
- // The number of milliseconds for which the device posture must be stable
- // before we perform an orientation change. If the device appears to be rotating
- // (being picked up, put down) then we keep waiting until it settles.
- private static final int SETTLE_TIME_MS = 200;
+ // Timestamp and value of the last accelerometer sample.
+ private long mLastFilteredTimestampNanos;
+ private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
+
+ // The last proposed rotation, -1 if unknown.
+ private int mProposedRotation;
+
+ // Value of the current predicted rotation, -1 if unknown.
+ private int mPredictedRotation;
+
+ // Timestamp of when the predicted rotation most recently changed.
+ private long mPredictedRotationTimestampNanos;
- // The maximum change in magnitude that can occur during the settle time.
- // Tuning this constant particularly helps to filter out situations where the
- // device is being picked up or put down by the user.
- private static final float SETTLE_MAGNITUDE_MAX_DELTA =
- SensorManager.STANDARD_GRAVITY * 0.2f;
+ // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
+ private long mFlatTimestampNanos;
- // The maximum change in tilt angle that can occur during the settle time.
- private static final int SETTLE_TILT_ANGLE_MAX_DELTA = 5;
+ // Timestamp when the device last appeared to be swinging.
+ private long mSwingTimestampNanos;
- // The maximum change in orientation angle that can occur during the settle time.
- private static final int SETTLE_ORIENTATION_ANGLE_MAX_DELTA = 5;
+ // History of observed tilt angles.
+ private static final int TILT_HISTORY_SIZE = 40;
+ private float[] mTiltHistory = new float[TILT_HISTORY_SIZE];
+ private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE];
+ private int mTiltHistoryIndex;
public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
mOrientationListener = orientationListener;
+ reset();
}
public int getProposedRotation() {
- return mProposalAgeMS >= SETTLE_TIME_MS ? mProposalRotation : -1;
+ return mProposedRotation;
}
@Override
@@ -359,8 +383,9 @@ public abstract class WindowOrientationListener {
float z = event.values[ACCELEROMETER_DATA_Z];
if (log) {
- Slog.v(TAG, "Raw acceleration vector: " +
- "x=" + x + ", y=" + y + ", z=" + z);
+ Slog.v(TAG, "Raw acceleration vector: "
+ + "x=" + x + ", y=" + y + ", z=" + z
+ + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
}
// Apply a low-pass filter to the acceleration up vector in cartesian space.
@@ -368,14 +393,16 @@ public abstract class WindowOrientationListener {
// or when we see values of (0, 0, 0) which indicates that we polled the
// accelerometer too soon after turning it on and we don't have any data yet.
final long now = event.timestamp;
- final float timeDeltaMS = (now - mLastTimestamp) * 0.000001f;
- boolean skipSample;
- if (timeDeltaMS <= 0 || timeDeltaMS > MAX_FILTER_DELTA_TIME_MS
+ final long then = mLastFilteredTimestampNanos;
+ final float timeDeltaMS = (now - then) * 0.000001f;
+ final boolean skipSample;
+ if (now < then
+ || now > then + MAX_FILTER_DELTA_TIME_NANOS
|| (x == 0 && y == 0 && z == 0)) {
if (log) {
Slog.v(TAG, "Resetting orientation listener.");
}
- clearProposal();
+ reset();
skipSample = true;
} else {
final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
@@ -383,27 +410,28 @@ public abstract class WindowOrientationListener {
y = alpha * (y - mLastFilteredY) + mLastFilteredY;
z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
if (log) {
- Slog.v(TAG, "Filtered acceleration vector: " +
- "x=" + x + ", y=" + y + ", z=" + z);
+ Slog.v(TAG, "Filtered acceleration vector: "
+ + "x=" + x + ", y=" + y + ", z=" + z
+ + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
}
skipSample = false;
}
- mLastTimestamp = now;
+ mLastFilteredTimestampNanos = now;
mLastFilteredX = x;
mLastFilteredY = y;
mLastFilteredZ = z;
- final int oldProposedRotation = getProposedRotation();
+ boolean isFlat = false;
+ boolean isSwinging = false;
if (!skipSample) {
// Calculate the magnitude of the acceleration vector.
- final float magnitude = (float) Math.sqrt(x * x + y * y + z * z);
+ final float magnitude = FloatMath.sqrt(x * x + y * y + z * z);
if (magnitude < MIN_ACCELERATION_MAGNITUDE
|| magnitude > MAX_ACCELERATION_MAGNITUDE) {
if (log) {
- Slog.v(TAG, "Ignoring sensor data, magnitude out of range: "
- + "magnitude=" + magnitude);
+ Slog.v(TAG, "Ignoring sensor data, magnitude out of range.");
}
- clearProposal();
+ clearPredictedRotation();
} else {
// Calculate the tilt angle.
// This is the angle between the up vector and the x-y plane (the plane of
@@ -414,14 +442,25 @@ public abstract class WindowOrientationListener {
final int tiltAngle = (int) Math.round(
Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
+ // Determine whether the device appears to be flat or swinging.
+ if (isFlat(now)) {
+ isFlat = true;
+ mFlatTimestampNanos = now;
+ }
+ if (isSwinging(now, tiltAngle)) {
+ isSwinging = true;
+ mSwingTimestampNanos = now;
+ }
+ addTiltHistoryEntry(now, tiltAngle);
+
// If the tilt angle is too close to horizontal then we cannot determine
// the orientation angle of the screen.
if (Math.abs(tiltAngle) > MAX_TILT) {
if (log) {
Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
- + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle);
+ + "tiltAngle=" + tiltAngle);
}
- clearProposal();
+ clearPredictedRotation();
} else {
// Calculate the orientation angle.
// This is the angle between the x-y projection of the up vector onto
@@ -439,89 +478,93 @@ public abstract class WindowOrientationListener {
nearestRotation = 0;
}
- // Determine the proposed orientation.
- // The confidence of the proposal is 1.0 when it is ideal and it
- // decays exponentially as the proposal moves further from the ideal
- // angle, tilt and magnitude of the proposed orientation.
- if (!isTiltAngleAcceptable(nearestRotation, tiltAngle)
- || !isOrientationAngleAcceptable(nearestRotation,
+ // Determine the predicted orientation.
+ if (isTiltAngleAcceptable(nearestRotation, tiltAngle)
+ && isOrientationAngleAcceptable(nearestRotation,
orientationAngle)) {
+ updatePredictedRotation(now, nearestRotation);
if (log) {
- Slog.v(TAG, "Ignoring sensor data, no proposal: "
- + "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle
- + ", orientationAngle=" + orientationAngle);
+ Slog.v(TAG, "Predicted: "
+ + "tiltAngle=" + tiltAngle
+ + ", orientationAngle=" + orientationAngle
+ + ", predictedRotation=" + mPredictedRotation
+ + ", predictedRotationAgeMS="
+ + ((now - mPredictedRotationTimestampNanos)
+ * 0.000001f));
}
- clearProposal();
} else {
if (log) {
- Slog.v(TAG, "Proposal: "
- + "magnitude=" + magnitude
- + ", tiltAngle=" + tiltAngle
- + ", orientationAngle=" + orientationAngle
- + ", proposalRotation=" + mProposalRotation);
+ Slog.v(TAG, "Ignoring sensor data, no predicted rotation: "
+ + "tiltAngle=" + tiltAngle
+ + ", orientationAngle=" + orientationAngle);
}
- updateProposal(nearestRotation, now / 1000000L,
- magnitude, tiltAngle, orientationAngle);
+ clearPredictedRotation();
}
}
}
}
+ // Determine new proposed rotation.
+ final int oldProposedRotation = mProposedRotation;
+ if (mPredictedRotation < 0 || isPredictedRotationAcceptable(now)) {
+ mProposedRotation = mPredictedRotation;
+ }
+
// Write final statistics about where we are in the orientation detection process.
- final int proposedRotation = getProposedRotation();
if (log) {
- final float proposalConfidence = Math.min(
- mProposalAgeMS * 1.0f / SETTLE_TIME_MS, 1.0f);
Slog.v(TAG, "Result: currentRotation=" + mOrientationListener.mCurrentRotation
- + ", proposedRotation=" + proposedRotation
+ + ", proposedRotation=" + mProposedRotation
+ + ", predictedRotation=" + mPredictedRotation
+ ", timeDeltaMS=" + timeDeltaMS
- + ", proposalRotation=" + mProposalRotation
- + ", proposalAgeMS=" + mProposalAgeMS
- + ", proposalConfidence=" + proposalConfidence);
+ + ", isFlat=" + isFlat
+ + ", isSwinging=" + isSwinging
+ + ", timeUntilSettledMS=" + remainingMS(now,
+ mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
+ + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
+ mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
+ + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
+ mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
}
// Tell the listener.
- if (proposedRotation != oldProposedRotation && proposedRotation >= 0) {
+ if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) {
if (log) {
- Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + proposedRotation
+ Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + mProposedRotation
+ ", oldProposedRotation=" + oldProposedRotation);
}
- mOrientationListener.onProposedRotationChanged(proposedRotation);
+ mOrientationListener.onProposedRotationChanged(mProposedRotation);
}
}
/**
- * Returns true if the tilt angle is acceptable for a proposed
- * orientation transition.
+ * Returns true if the tilt angle is acceptable for a given predicted rotation.
*/
- private boolean isTiltAngleAcceptable(int proposedRotation,
- int tiltAngle) {
- return tiltAngle >= TILT_TOLERANCE[proposedRotation][0]
- && tiltAngle <= TILT_TOLERANCE[proposedRotation][1];
+ private boolean isTiltAngleAcceptable(int rotation, int tiltAngle) {
+ return tiltAngle >= TILT_TOLERANCE[rotation][0]
+ && tiltAngle <= TILT_TOLERANCE[rotation][1];
}
/**
- * Returns true if the orientation angle is acceptable for a proposed
- * orientation transition.
+ * Returns true if the orientation angle is acceptable for a given predicted rotation.
*
* This function takes into account the gap between adjacent orientations
* for hysteresis.
*/
- private boolean isOrientationAngleAcceptable(int proposedRotation, int orientationAngle) {
+ private boolean isOrientationAngleAcceptable(int rotation, int orientationAngle) {
// If there is no current rotation, then there is no gap.
// The gap is used only to introduce hysteresis among advertised orientation
// changes to avoid flapping.
final int currentRotation = mOrientationListener.mCurrentRotation;
if (currentRotation >= 0) {
- // If the proposed rotation is the same or is counter-clockwise adjacent,
- // then we set a lower bound on the orientation angle.
+ // If the specified rotation is the same or is counter-clockwise adjacent
+ // to the current rotation, then we set a lower bound on the orientation angle.
// For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90,
// then we want to check orientationAngle > 45 + GAP / 2.
- if (proposedRotation == currentRotation
- || proposedRotation == (currentRotation + 1) % 4) {
- int lowerBound = proposedRotation * 90 - 45
+ if (rotation == currentRotation
+ || rotation == (currentRotation + 1) % 4) {
+ int lowerBound = rotation * 90 - 45
+ ADJACENT_ORIENTATION_ANGLE_GAP / 2;
- if (proposedRotation == 0) {
+ if (rotation == 0) {
if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
return false;
}
@@ -532,15 +575,15 @@ public abstract class WindowOrientationListener {
}
}
- // If the proposed rotation is the same or is clockwise adjacent,
+ // If the specified rotation is the same or is clockwise adjacent,
// then we set an upper bound on the orientation angle.
- // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_270,
+ // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270,
// then we want to check orientationAngle < 315 - GAP / 2.
- if (proposedRotation == currentRotation
- || proposedRotation == (currentRotation + 3) % 4) {
- int upperBound = proposedRotation * 90 + 45
+ if (rotation == currentRotation
+ || rotation == (currentRotation + 3) % 4) {
+ int upperBound = rotation * 90 + 45
- ADJACENT_ORIENTATION_ANGLE_GAP / 2;
- if (proposedRotation == 0) {
+ if (rotation == 0) {
if (orientationAngle <= 45 && orientationAngle > upperBound) {
return false;
}
@@ -554,58 +597,97 @@ public abstract class WindowOrientationListener {
return true;
}
- private void clearProposal() {
- mProposalRotation = -1;
- mProposalAgeMS = 0;
- }
+ /**
+ * Returns true if the predicted rotation is ready to be advertised as a
+ * proposed rotation.
+ */
+ private boolean isPredictedRotationAcceptable(long now) {
+ // The predicted rotation must have settled long enough.
+ if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
+ return false;
+ }
+
+ // The last flat state (time since picked up) must have been sufficiently long ago.
+ if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
+ return false;
+ }
- private void updateProposal(int rotation, long timestampMS,
- float magnitude, int tiltAngle, int orientationAngle) {
- if (mProposalRotation != rotation) {
- mProposalRotation = rotation;
- mHistoryIndex = 0;
- mHistoryLength = 0;
+ // The last swing state (time since last movement to put down) must have been
+ // sufficiently long ago.
+ if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
+ return false;
}
- final int index = mHistoryIndex;
- mHistoryTimestampMS[index] = timestampMS;
- mHistoryMagnitudes[index] = magnitude;
- mHistoryTiltAngles[index] = tiltAngle;
- mHistoryOrientationAngles[index] = orientationAngle;
- mHistoryIndex = (index + 1) % HISTORY_SIZE;
- if (mHistoryLength < HISTORY_SIZE) {
- mHistoryLength += 1;
+ // Looks good!
+ return true;
+ }
+
+ private void reset() {
+ mLastFilteredTimestampNanos = Long.MIN_VALUE;
+ mProposedRotation = -1;
+ mFlatTimestampNanos = Long.MIN_VALUE;
+ mSwingTimestampNanos = Long.MIN_VALUE;
+ clearPredictedRotation();
+ clearTiltHistory();
+ }
+
+ private void clearPredictedRotation() {
+ mPredictedRotation = -1;
+ mPredictedRotationTimestampNanos = Long.MIN_VALUE;
+ }
+
+ private void updatePredictedRotation(long now, int rotation) {
+ if (mPredictedRotation != rotation) {
+ mPredictedRotation = rotation;
+ mPredictedRotationTimestampNanos = now;
}
+ }
- long age = 0;
- for (int i = 1; i < mHistoryLength; i++) {
- final int olderIndex = (index + HISTORY_SIZE - i) % HISTORY_SIZE;
- if (Math.abs(mHistoryMagnitudes[olderIndex] - magnitude)
- > SETTLE_MAGNITUDE_MAX_DELTA) {
+ private void clearTiltHistory() {
+ mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
+ mTiltHistoryIndex = 1;
+ }
+
+ private void addTiltHistoryEntry(long now, float tilt) {
+ mTiltHistory[mTiltHistoryIndex] = tilt;
+ mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now;
+ mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE;
+ mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE;
+ }
+
+ private boolean isFlat(long now) {
+ for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
+ if (mTiltHistory[i] < FLAT_ANGLE) {
break;
}
- if (angleAbsoluteDelta(mHistoryTiltAngles[olderIndex],
- tiltAngle) > SETTLE_TILT_ANGLE_MAX_DELTA) {
- break;
+ if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) {
+ // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
+ return true;
}
- if (angleAbsoluteDelta(mHistoryOrientationAngles[olderIndex],
- orientationAngle) > SETTLE_ORIENTATION_ANGLE_MAX_DELTA) {
+ }
+ return false;
+ }
+
+ private boolean isSwinging(long now, float tilt) {
+ for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
+ if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) {
break;
}
- age = timestampMS - mHistoryTimestampMS[olderIndex];
- if (age >= SETTLE_TIME_MS) {
- break;
+ if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
+ // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
+ return true;
}
}
- mProposalAgeMS = age;
+ return false;
}
- private static int angleAbsoluteDelta(int a, int b) {
- int delta = Math.abs(a - b);
- if (delta > 180) {
- delta = 360 - delta;
- }
- return delta;
+ private int nextTiltHistoryIndex(int index) {
+ index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
+ return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
+ }
+
+ private static float remainingMS(long now, long until) {
+ return now >= until ? 0 : (until - now) * 0.000001f;
}
}
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 5ec1ec3..bd02d62 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -193,10 +193,12 @@ public class BaseInputConnection implements InputConnection {
/**
* The default implementation performs the deletion around the current
* selection position of the editable text.
+ * @param beforeLength
+ * @param afterLength
*/
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength
- + " / " + rightLength);
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
+ + " / " + afterLength);
final Editable content = getEditable();
if (content == null) return false;
@@ -226,17 +228,17 @@ public class BaseInputConnection implements InputConnection {
int deleted = 0;
- if (leftLength > 0) {
- int start = a - leftLength;
+ if (beforeLength > 0) {
+ int start = a - beforeLength;
if (start < 0) start = 0;
content.delete(start, a);
deleted = a - start;
}
- if (rightLength > 0) {
+ if (afterLength > 0) {
b = b - deleted;
- int end = b + rightLength;
+ int end = b + afterLength;
if (end > content.length()) end = content.length();
content.delete(b, end);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index a6639d1..3563d4d 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -138,19 +138,21 @@ public interface InputConnection {
int flags);
/**
- * Delete <var>leftLength</var> characters of text before the current cursor
- * position, and delete <var>rightLength</var> characters of text after the
- * current cursor position, excluding composing text.
+ * Delete <var>beforeLength</var> characters of text before the current cursor
+ * position, and delete <var>afterLength</var> characters of text after the
+ * current cursor position, excluding composing text. Before and after refer
+ * to the order of the characters in the string, not to their visual representation.
*
- * @param leftLength The number of characters to be deleted before the
+ *
+ * @param beforeLength The number of characters to be deleted before the
* current cursor position.
- * @param rightLength The number of characters to be deleted after the
+ * @param afterLength The number of characters to be deleted after the
* current cursor position.
- *
+ *
* @return Returns true on success, false if the input connection is no longer
* valid.
*/
- public boolean deleteSurroundingText(int leftLength, int rightLength);
+ public boolean deleteSurroundingText(int beforeLength, int afterLength);
/**
* Set composing text around the current cursor position with the given text,
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 690ea85..a48473e 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -62,8 +62,8 @@ public class InputConnectionWrapper implements InputConnection {
return mTarget.getExtractedText(request, flags);
}
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- return mTarget.deleteSurroundingText(leftLength, rightLength);
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+ return mTarget.deleteSurroundingText(beforeLength, afterLength);
}
public boolean setComposingText(CharSequence text, int newCursorPosition) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index b41e6f5..0985e14 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -651,19 +651,7 @@ public final class InputMethodManager {
}
}
- if (mServedInputConnection != null) {
- // We need to tell the previously served view that it is no
- // longer the input target, so it can reset its state. Schedule
- // this call on its window's Handler so it will be on the correct
- // thread and outside of our lock.
- Handler vh = mServedView.getHandler();
- if (vh != null) {
- // This will result in a call to reportFinishInputConnection()
- // below.
- vh.sendMessage(vh.obtainMessage(ViewRootImpl.FINISH_INPUT_CONNECTION,
- mServedInputConnection));
- }
- }
+ notifyInputConnectionFinished();
mServedView = null;
mCompletions = null;
@@ -671,7 +659,25 @@ public final class InputMethodManager {
clearConnectionLocked();
}
}
-
+
+ /**
+ * Notifies the served view that the current InputConnection will no longer be used.
+ */
+ private void notifyInputConnectionFinished() {
+ if (mServedView != null && mServedInputConnection != null) {
+ // We need to tell the previously served view that it is no
+ // longer the input target, so it can reset its state. Schedule
+ // this call on its window's Handler so it will be on the correct
+ // thread and outside of our lock.
+ Handler vh = mServedView.getHandler();
+ if (vh != null) {
+ // This will result in a call to reportFinishInputConnection() below.
+ vh.sendMessage(vh.obtainMessage(ViewRootImpl.FINISH_INPUT_CONNECTION,
+ mServedInputConnection));
+ }
+ }
+ }
+
/**
* Called from the FINISH_INPUT_CONNECTION message above.
* @hide
@@ -681,7 +687,7 @@ public final class InputMethodManager {
ic.finishComposingText();
}
}
-
+
public void displayCompletions(View view, CompletionInfo[] completions) {
checkFocus();
synchronized (mH) {
@@ -831,7 +837,7 @@ public final class InputMethodManager {
* shown with {@link #SHOW_FORCED}.
*/
public static final int HIDE_NOT_ALWAYS = 0x0002;
-
+
/**
* Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}
* without a result: request to hide the soft input window from the
@@ -993,7 +999,7 @@ public final class InputMethodManager {
tba.fieldId = view.getId();
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
-
+
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
@@ -1012,6 +1018,8 @@ public final class InputMethodManager {
// Hook 'em up and let 'er rip.
mCurrentTextBoxAttribute = tba;
mServedConnecting = false;
+ // Notify the served view that its previous input connection is finished
+ notifyInputConnectionFinished();
mServedInputConnection = ic;
IInputContext servedContext;
if (ic != null) {
@@ -1115,7 +1123,7 @@ public final class InputMethodManager {
}
}
- void scheduleCheckFocusLocked(View view) {
+ static void scheduleCheckFocusLocked(View view) {
Handler vh = view.getHandler();
if (vh != null && !vh.hasMessages(ViewRootImpl.CHECK_FOCUS)) {
// This will result in a call to checkFocus() below.
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 21364c1..bc0557e 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -198,6 +198,10 @@ public class HTML5VideoFullScreen extends HTML5VideoView
// Call into the native to ask for the state, if still in play mode,
// this will trigger the video to play.
mProxy.dispatchOnRestoreState();
+
+ if (getStartWhenPrepared()) {
+ mPlayer.start();
+ }
}
public boolean fullScreenExited() {
diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java
index 1d8bda7..73166cb 100644
--- a/core/java/android/webkit/HTML5VideoView.java
+++ b/core/java/android/webkit/HTML5VideoView.java
@@ -194,20 +194,9 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
mPlayer.setOnInfoListener(proxy);
}
- // Normally called immediately after setVideoURI. But for full screen,
- // this should be after surface holder created
- public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
- // SurfaceTexture will be created lazily here for inline mode
- decideDisplayMode();
-
- setOnCompletionListener(proxy);
- setOnPreparedListener(proxy);
- setOnErrorListener(proxy);
- setOnInfoListener(proxy);
- // When there is exception, we could just bail out silently.
- // No Video will be played though. Write the stack for debug
+ public void prepareDataCommon(HTML5VideoViewProxy proxy) {
try {
- mPlayer.setDataSource(mProxy.getContext(), mUri, mHeaders);
+ mPlayer.setDataSource(proxy.getContext(), mUri, mHeaders);
mPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
e.printStackTrace();
@@ -219,6 +208,25 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
mCurrentState = STATE_NOTPREPARED;
}
+ public void reprepareData(HTML5VideoViewProxy proxy) {
+ mPlayer.reset();
+ prepareDataCommon(proxy);
+ }
+
+ // Normally called immediately after setVideoURI. But for full screen,
+ // this should be after surface holder created
+ public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
+ // SurfaceTexture will be created lazily here for inline mode
+ decideDisplayMode();
+
+ setOnCompletionListener(proxy);
+ setOnPreparedListener(proxy);
+ setOnErrorListener(proxy);
+ setOnInfoListener(proxy);
+
+ prepareDataCommon(proxy);
+ }
+
// Common code
public int getVideoLayerId() {
@@ -324,4 +332,14 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
return false;
}
+ private boolean m_startWhenPrepared = false;
+
+ public void setStartWhenPrepared(boolean willPlay) {
+ m_startWhenPrepared = willPlay;
+ }
+
+ public boolean getStartWhenPrepared() {
+ return m_startWhenPrepared;
+ }
+
}
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index 1c09bb9..d306c86 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -182,6 +182,21 @@ class HTML5VideoViewProxy extends Handler
if (mHTML5VideoView != null) {
currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
+
+ // When playing video back to back in full screen mode,
+ // javascript will switch the src and call play.
+ // In this case, we can just reuse the same full screen view,
+ // and play the video after prepared.
+ if (mHTML5VideoView.isFullScreenMode()
+ && !backFromFullScreenMode
+ && currentVideoLayerId != videoLayerId
+ && mCurrentProxy != proxy) {
+ mCurrentProxy = proxy;
+ mHTML5VideoView.setStartWhenPrepared(true);
+ mHTML5VideoView.setVideoURI(url, proxy);
+ mHTML5VideoView.reprepareData(proxy);
+ return;
+ }
}
if (backFromFullScreenMode
diff --git a/core/java/android/webkit/SelectActionModeCallback.java b/core/java/android/webkit/SelectActionModeCallback.java
index 8c174aa..cdf20f6 100644
--- a/core/java/android/webkit/SelectActionModeCallback.java
+++ b/core/java/android/webkit/SelectActionModeCallback.java
@@ -17,6 +17,7 @@
package android.webkit;
import android.app.SearchManager;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.provider.Browser;
@@ -27,11 +28,16 @@ import android.view.MenuItem;
class SelectActionModeCallback implements ActionMode.Callback {
private WebView mWebView;
private ActionMode mActionMode;
+ private boolean mIsTextSelected = true;
void setWebView(WebView webView) {
mWebView = webView;
}
+ void setTextSelected(boolean isTextSelected) {
+ mIsTextSelected = isTextSelected;
+ }
+
void finish() {
// It is possible that onCreateActionMode was never called, in the case
// where there is no ActionBar, for example.
@@ -52,17 +58,25 @@ class SelectActionModeCallback implements ActionMode.Callback {
mode.setTitle(allowText ?
context.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
- if (!mode.isUiFocusable()) {
- // If the action mode UI we're running in isn't capable of taking window focus
- // the user won't be able to type into the find on page UI. Disable this functionality.
- // (Note that this should only happen in floating dialog windows.)
- // This can be removed once we can handle multiple focusable windows at a time
- // in a better way.
- final MenuItem findOnPageItem = menu.findItem(com.android.internal.R.id.find);
- if (findOnPageItem != null) {
- findOnPageItem.setVisible(false);
- }
- }
+ // If the action mode UI we're running in isn't capable of taking window focus
+ // the user won't be able to type into the find on page UI. Disable this functionality.
+ // (Note that this should only happen in floating dialog windows.)
+ // This can be removed once we can handle multiple focusable windows at a time
+ // in a better way.
+ ClipboardManager cm = (ClipboardManager)(context
+ .getSystemService(Context.CLIPBOARD_SERVICE));
+ boolean isFocusable = mode.isUiFocusable();
+ boolean isEditable = mWebView.focusCandidateIsEditableText();
+ boolean canPaste = isEditable && cm.hasPrimaryClip() && isFocusable;
+ boolean canFind = !isEditable && isFocusable;
+ boolean canCut = isEditable && mIsTextSelected && isFocusable;
+ boolean canCopy = mIsTextSelected;
+ boolean canWebSearch = mIsTextSelected;
+ setMenuVisibility(menu, canFind, com.android.internal.R.id.find);
+ setMenuVisibility(menu, canPaste, com.android.internal.R.id.paste);
+ setMenuVisibility(menu, canCut, com.android.internal.R.id.cut);
+ setMenuVisibility(menu, canCopy, com.android.internal.R.id.copy);
+ setMenuVisibility(menu, canWebSearch, com.android.internal.R.id.websearch);
mActionMode = mode;
return true;
}
@@ -75,11 +89,21 @@ class SelectActionModeCallback implements ActionMode.Callback {
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch(item.getItemId()) {
+ case android.R.id.cut:
+ mWebView.cutSelection();
+ mode.finish();
+ break;
+
case android.R.id.copy:
mWebView.copySelection();
mode.finish();
break;
+ case android.R.id.paste:
+ mWebView.pasteFromClipboard();
+ mode.finish();
+ break;
+
case com.android.internal.R.id.share:
String selection = mWebView.getSelection();
Browser.sendString(mWebView.getContext(), selection);
@@ -113,4 +137,11 @@ class SelectActionModeCallback implements ActionMode.Callback {
public void onDestroyActionMode(ActionMode mode) {
mWebView.selectionDone();
}
+
+ private void setMenuVisibility(Menu menu, boolean visible, int resourceId) {
+ final MenuItem item = menu.findItem(resourceId);
+ if (item != null) {
+ item.setVisible(visible);
+ }
+ }
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index c3b6416..b255c57 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -20,6 +20,7 @@ import android.annotation.Widget;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -59,6 +60,10 @@ import android.os.StrictMode;
import android.os.SystemClock;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.Selection;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
@@ -362,39 +367,126 @@ public class WebView extends AbsoluteLayout
}
/**
- * InputConnection used for ContentEditable. This captures the 'delete'
- * commands and sends delete key presses.
+ * InputConnection used for ContentEditable. This captures changes
+ * to the text and sends them either as key strokes or text changes.
*/
private class WebViewInputConnection extends BaseInputConnection {
+ // Used for mapping characters to keys typed.
+ private KeyCharacterMap mKeyCharacterMap;
+
public WebViewInputConnection() {
- super(WebView.this, false);
+ super(WebView.this, true);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ Editable editable = getEditable();
+ int start = getComposingSpanStart(editable);
+ int end = getComposingSpanEnd(editable);
+ if (start < 0 || end < 0) {
+ start = Selection.getSelectionStart(editable);
+ end = Selection.getSelectionEnd(editable);
+ }
+ if (end < start) {
+ int temp = end;
+ end = start;
+ start = temp;
+ }
+ setNewText(start, end, text);
+ return super.setComposingText(text, newCursorPosition);
+ }
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ setComposingText(text, newCursorPosition);
+ finishComposingText();
+ return true;
+ }
+
+ @Override
+ public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ Editable editable = getEditable();
+ int cursorPosition = Selection.getSelectionEnd(editable);
+ int startDelete = Math.max(0, cursorPosition - leftLength);
+ int endDelete = Math.min(editable.length(),
+ cursorPosition + rightLength);
+ setNewText(startDelete, endDelete, "");
+ return super.deleteSurroundingText(leftLength, rightLength);
+ }
+
+ /**
+ * Sends a text change to webkit indirectly. If it is a single-
+ * character add or delete, it sends it as a key stroke. If it cannot
+ * be represented as a key stroke, it sends it as a field change.
+ * @param start The start offset (inclusive) of the text being changed.
+ * @param end The end offset (exclusive) of the text being changed.
+ * @param text The new text to replace the changed text.
+ */
+ private void setNewText(int start, int end, CharSequence text) {
+ Editable editable = getEditable();
+ CharSequence original = editable.subSequence(start, end);
+ boolean isCharacterAdd = false;
+ boolean isCharacterDelete = false;
+ int textLength = text.length();
+ int originalLength = original.length();
+ if (textLength > originalLength) {
+ isCharacterAdd = (textLength == originalLength + 1)
+ && TextUtils.regionMatches(text, 0, original, 0,
+ originalLength);
+ } else if (originalLength > textLength) {
+ isCharacterDelete = (textLength == originalLength - 1)
+ && TextUtils.regionMatches(text, 0, original, 0,
+ textLength);
+ }
+ if (isCharacterAdd) {
+ sendCharacter(text.charAt(textLength - 1));
+ mTextGeneration++;
+ } else if (isCharacterDelete) {
+ sendDeleteKey();
+ mTextGeneration++;
+ } else if (textLength != originalLength ||
+ !TextUtils.regionMatches(text, 0, original, 0,
+ textLength)) {
+ // Send a message so that key strokes and text replacement
+ // do not come out of order.
+ Message replaceMessage = mPrivateHandler.obtainMessage(
+ REPLACE_TEXT, start, end, text.toString());
+ mPrivateHandler.sendMessage(replaceMessage);
+ }
+ }
+
+ /**
+ * Send a single character to the WebView as a key down and up event.
+ * @param c The character to be sent.
+ */
+ private void sendCharacter(char c) {
+ if (mKeyCharacterMap == null) {
+ mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ }
+ char[] chars = new char[1];
+ chars[0] = c;
+ KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
+ if (events != null) {
+ for (KeyEvent event : events) {
+ sendKeyEvent(event);
+ }
+ }
}
- private void sendKeyPress(int keyCode) {
+ /**
+ * Send the delete character as a key down and up event.
+ */
+ private void sendDeleteKey() {
long eventTime = SystemClock.uptimeMillis();
sendKeyEvent(new KeyEvent(eventTime, eventTime,
- KeyEvent.ACTION_DOWN, keyCode, 0, 0,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD));
sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
- KeyEvent.ACTION_UP, keyCode, 0, 0,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD));
}
-
- @Override
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
- // Look for one-character delete and send it as a key press.
- if (leftLength == 1 && rightLength == 0) {
- sendKeyPress(KeyEvent.KEYCODE_DEL);
- } else if (leftLength == 0 && rightLength == 1){
- sendKeyPress(KeyEvent.KEYCODE_FORWARD_DEL);
- } else if (mWebViewCore != null) {
- mWebViewCore.sendMessage(EventHub.DELETE_SURROUNDING_TEXT,
- leftLength, rightLength);
- }
- return super.deleteSurroundingText(leftLength, rightLength);
- }
}
@@ -422,7 +514,7 @@ public class WebView extends AbsoluteLayout
private final Rect mViewRectViewport = new Rect();
private final RectF mVisibleContentRect = new RectF();
private boolean mGLViewportEmpty = false;
- WebViewInputConnection mInputConnection = new WebViewInputConnection();
+ WebViewInputConnection mInputConnection = null;
/**
@@ -712,13 +804,11 @@ public class WebView extends AbsoluteLayout
static boolean sDisableNavcache = false;
// the color used to highlight the touch rectangles
- private static final int HIGHLIGHT_COLOR = 0x6633b5e5;
- // the round corner for the highlight path
- private static final float TOUCH_HIGHLIGHT_ARC = 5.0f;
+ static final int HIGHLIGHT_COLOR = 0x6633b5e5;
// the region indicating where the user touched on the screen
private Region mTouchHighlightRegion = new Region();
// the paint for the touch highlight
- private Paint mTouchHightlightPaint;
+ private Paint mTouchHightlightPaint = new Paint();
// debug only
private static final boolean DEBUG_TOUCH_HIGHLIGHT = true;
private static final int TOUCH_HIGHLIGHT_ELAPSE_TIME = 2000;
@@ -799,6 +889,10 @@ public class WebView extends AbsoluteLayout
static final int UPDATE_ZOOM_DENSITY = 139;
static final int EXIT_FULLSCREEN_VIDEO = 140;
+ static final int COPY_TO_CLIPBOARD = 141;
+ static final int INIT_EDIT_FIELD = 142;
+ static final int REPLACE_TEXT = 143;
+
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -4430,10 +4524,6 @@ public class WebView extends AbsoluteLayout
Rect r = mTouchHighlightRegion.getBounds();
postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
} else {
- if (mTouchHightlightPaint == null) {
- mTouchHightlightPaint = new Paint();
- mTouchHightlightPaint.setColor(HIGHLIGHT_COLOR);
- }
RegionIterator iter = new RegionIterator(mTouchHighlightRegion);
Rect r = new Rect();
while (iter.next(r)) {
@@ -4526,6 +4616,11 @@ public class WebView extends AbsoluteLayout
final boolean isSelecting = selectText();
if (isSelecting) {
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ } else if (focusCandidateIsEditableText()) {
+ mSelectCallback = new SelectActionModeCallback();
+ mSelectCallback.setWebView(this);
+ mSelectCallback.setTextSelected(false);
+ startActionMode(mSelectCallback);
}
return isSelecting;
}
@@ -4948,15 +5043,19 @@ public class WebView extends AbsoluteLayout
}
@Override
- public boolean onCheckIsTextEditor() {
- return true;
- }
-
- @Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN
+ outAttrs.inputType = EditorInfo.IME_FLAG_NO_FULLSCREEN
| EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_NORMAL;
+ | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
+ | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
+ outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
+
+ if (mInputConnection == null) {
+ mInputConnection = new WebViewInputConnection();
+ }
+ outAttrs.initialCapsMode = mInputConnection.getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
return mInputConnection;
}
@@ -5737,12 +5836,49 @@ public class WebView extends AbsoluteLayout
ClipboardManager cm = (ClipboardManager)getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(selection);
+ int[] handles = new int[4];
+ nativeGetSelectionHandles(mNativeClass, handles);
+ mWebViewCore.sendMessage(EventHub.COPY_TEXT, handles);
}
invalidate(); // remove selection region and pointer
return copiedSomething;
}
/**
+ * Cut the selected text into the clipboard
+ *
+ * @hide This is an implementation detail
+ */
+ public void cutSelection() {
+ copySelection();
+ int[] handles = new int[4];
+ nativeGetSelectionHandles(mNativeClass, handles);
+ mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles);
+ }
+
+ /**
+ * Paste text from the clipboard to the cursor position.
+ *
+ * @hide This is an implementation detail
+ */
+ public void pasteFromClipboard() {
+ ClipboardManager cm = (ClipboardManager)getContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clipData = cm.getPrimaryClip();
+ if (clipData != null) {
+ ClipData.Item clipItem = clipData.getItemAt(0);
+ CharSequence pasteText = clipItem.getText();
+ if (pasteText != null) {
+ int[] handles = new int[4];
+ nativeGetSelectionHandles(mNativeClass, handles);
+ mWebViewCore.sendMessage(EventHub.DELETE_TEXT, handles);
+ mWebViewCore.sendMessage(EventHub.INSERT_TEXT,
+ pasteText.toString());
+ }
+ }
+ }
+
+ /**
* @hide This is an implementation detail.
*/
public SearchBox getSearchBox() {
@@ -8874,7 +9010,7 @@ public class WebView extends AbsoluteLayout
case HIT_TEST_RESULT:
WebKitHitTest hit = (WebKitHitTest) msg.obj;
mFocusedNode = hit;
- setTouchHighlightRects(hit != null ? hit.mTouchRects : null);
+ setTouchHighlightRects(hit);
if (hit == null) {
mInitialHitTestResult = null;
} else {
@@ -8922,6 +9058,35 @@ public class WebView extends AbsoluteLayout
nativeSelectAt(msg.arg1, msg.arg2);
break;
+ case COPY_TO_CLIPBOARD:
+ copyToClipboard((String) msg.obj);
+ break;
+
+ case INIT_EDIT_FIELD:
+ if (mInputConnection != null) {
+ mTextGeneration = 0;
+ String text = (String)msg.obj;
+ mInputConnection.beginBatchEdit();
+ Editable editable = mInputConnection.getEditable();
+ editable.replace(0, editable.length(), text);
+ int start = msg.arg1;
+ int end = msg.arg2;
+ mInputConnection.setComposingRegion(end, end);
+ mInputConnection.setSelection(start, end);
+ mInputConnection.endBatchEdit();
+ }
+ break;
+
+ case REPLACE_TEXT:{
+ String text = (String)msg.obj;
+ int start = msg.arg1;
+ int end = msg.arg2;
+ int cursorPosition = start + text.length();
+ replaceTextfieldText(start, end, text,
+ cursorPosition, cursorPosition);
+ break;
+ }
+
default:
super.handleMessage(msg);
break;
@@ -8929,12 +9094,14 @@ public class WebView extends AbsoluteLayout
}
}
- private void setTouchHighlightRects(Rect[] rects) {
+ private void setTouchHighlightRects(WebKitHitTest hit) {
+ Rect[] rects = hit != null ? hit.mTouchRects : null;
if (!mTouchHighlightRegion.isEmpty()) {
invalidate(mTouchHighlightRegion.getBounds());
mTouchHighlightRegion.setEmpty();
}
if (rects != null) {
+ mTouchHightlightPaint.setColor(hit.mTapHighlightColor);
for (Rect rect : rects) {
Rect viewRect = contentToViewRect(rect);
// some sites, like stories in nytimes.com, set
@@ -9045,10 +9212,13 @@ public class WebView extends AbsoluteLayout
*/
private void updateTextSelectionFromMessage(int nodePointer,
int textGeneration, WebViewCore.TextSelectionData data) {
- if (inEditingMode()
- && mWebTextView.isSameTextField(nodePointer)
- && textGeneration == mTextGeneration) {
- mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd);
+ if (textGeneration == mTextGeneration) {
+ if (inEditingMode()
+ && mWebTextView.isSameTextField(nodePointer)) {
+ mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd);
+ } else if (mInputConnection != null){
+ mInputConnection.setSelection(data.mStart, data.mEnd);
+ }
}
}
@@ -9598,6 +9768,18 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Copy text into the clipboard. This is called indirectly from
+ * WebViewCore.
+ * @param text The text to put into the clipboard.
+ */
+ private void copyToClipboard(String text) {
+ ClipboardManager cm = (ClipboardManager)getContext()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText(getTitle(), text);
+ cm.setPrimaryClip(clip);
+ }
+
+ /**
* Update our cache with updatedText.
* @param updatedText The new text to put in our cache.
* @hide
@@ -9683,6 +9865,23 @@ public class WebView extends AbsoluteLayout
return nativeTileProfilingGetFloat(frame, tile, key);
}
+ /**
+ * Checks the focused content for an editable text field. This can be
+ * text input or ContentEditable.
+ * @return true if the focused item is an editable text field.
+ */
+ boolean focusCandidateIsEditableText() {
+ boolean isEditable = false;
+ // TODO: reverse sDisableNavcache so that its name is positive
+ boolean isNavcacheEnabled = !sDisableNavcache;
+ if (isNavcacheEnabled) {
+ isEditable = nativeFocusCandidateIsEditableText(mNativeClass);
+ } else if (mFocusedNode != null) {
+ isEditable = mFocusedNode.mEditable;
+ }
+ return isEditable;
+ }
+
private native int nativeCacheHitFramePointer();
private native boolean nativeCacheHitIsPlugin();
private native Rect nativeCacheHitNodeBounds();
@@ -9728,6 +9927,7 @@ public class WebView extends AbsoluteLayout
/* package */ native boolean nativeFocusCandidateIsPassword();
private native boolean nativeFocusCandidateIsRtlText();
private native boolean nativeFocusCandidateIsTextInput();
+ private native boolean nativeFocusCandidateIsEditableText(int nativeClass);
/* package */ native int nativeFocusCandidateMaxLength();
/* package */ native boolean nativeFocusCandidateIsAutoComplete();
/* package */ native boolean nativeFocusCandidateIsSpellcheck();
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index bfca07c..fe51581 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -647,18 +647,6 @@ public final class WebViewCore {
int end, int textGeneration);
/**
- * Delete text near the cursor.
- * @param nativeClass The pointer to the native class (mNativeClass)
- * @param leftLength The number of characters to the left of the cursor to
- * delete
- * @param rightLength The number of characters to the right of the cursor
- * to delete.
- */
- private native void nativeDeleteSurroundingText(int nativeClass,
- int leftLength,
- int rightLength);
-
- /**
* Set the selection to (start, end) in the focused textfield. If start and
* end are out of order, swap them.
* @param nativeClass Pointer to the C++ WebViewCore object mNativeClass
@@ -881,6 +869,7 @@ public final class WebViewCore {
String mTitle;
Rect[] mTouchRects;
boolean mEditable;
+ int mTapHighlightColor = WebView.HIGHLIGHT_COLOR;
// These are the input values that produced this hit test
int mHitTestX;
@@ -1125,6 +1114,11 @@ public final class WebViewCore {
// private message ids
private static final int DESTROY = 200;
+ // for cut & paste
+ static final int COPY_TEXT = 210;
+ static final int DELETE_TEXT = 211;
+ static final int INSERT_TEXT = 212;
+
// Private handler for WebCore messages.
private Handler mHandler;
// Message queue for containing messages before the WebCore thread is
@@ -1570,11 +1564,6 @@ public final class WebViewCore {
deleteSelectionData.mStart, deleteSelectionData.mEnd, msg.arg1);
break;
- case DELETE_SURROUNDING_TEXT:
- nativeDeleteSurroundingText(mNativeClass,
- msg.arg1, msg.arg2);
- break;
-
case SET_SELECTION:
nativeSetSelection(mNativeClass, msg.arg1, msg.arg2);
break;
@@ -1736,6 +1725,28 @@ public final class WebViewCore {
Rect rect = (Rect) msg.obj;
nativeScrollLayer(mNativeClass, nativeLayer,
rect);
+ break;
+
+ case DELETE_TEXT: {
+ int[] handles = (int[]) msg.obj;
+ nativeDeleteText(mNativeClass, handles[0],
+ handles[1], handles[2], handles[3]);
+ break;
+ }
+ case COPY_TEXT: {
+ int[] handles = (int[]) msg.obj;
+ String copiedText = nativeGetText(mNativeClass,
+ handles[0], handles[1], handles[2],
+ handles[3]);
+ if (copiedText != null) {
+ mWebView.mPrivateHandler.obtainMessage(WebView.COPY_TO_CLIPBOARD, copiedText)
+ .sendToTarget();
+ }
+ break;
+ }
+ case INSERT_TEXT:
+ nativeInsertText(mNativeClass, (String) msg.obj);
+ break;
}
}
};
@@ -2711,6 +2722,15 @@ public final class WebViewCore {
WebView.FIND_AGAIN).sendToTarget();
}
+ // called by JNI
+ private void initEditField(String text, int start, int end) {
+ if (mWebView == null) {
+ return;
+ }
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.INIT_EDIT_FIELD, start, end, text).sendToTarget();
+ }
+
private native void nativeUpdateFrameCacheIfLoading(int nativeClass);
private native void nativeRevealSelection(int nativeClass);
private native String nativeRequestLabel(int nativeClass, int framePtr,
@@ -2975,4 +2995,35 @@ public final class WebViewCore {
private native void nativeAutoFillForm(int nativeClass, int queryId);
private native void nativeScrollLayer(int nativeClass, int layer, Rect rect);
+
+ /**
+ * Deletes editable text between two points. Note that the selection may
+ * differ from the WebView's selection because the algorithms for selecting
+ * text differs for non-LTR text. Any text that isn't editable will be
+ * left unchanged.
+ * @param nativeClass The pointer to the native class (mNativeClass)
+ * @param startX The X position of the top-left selection point.
+ * @param startY The Y position of the top-left selection point.
+ * @param endX The X position of the bottom-right selection point.
+ * @param endY The Y position of the bottom-right selection point.
+ */
+ private native void nativeDeleteText(int nativeClass,
+ int startX, int startY, int endX, int endY);
+ /**
+ * Inserts text at the current cursor position. If the currently-focused
+ * node does not have a cursor position then this function does nothing.
+ */
+ private native void nativeInsertText(int nativeClass, String text);
+ /**
+ * Gets the text between two selection points. Note that the selection
+ * may differ from the WebView's selection because the algorithms for
+ * selecting text differs for non-LTR text.
+ * @param nativeClass The pointer to the native class (mNativeClass)
+ * @param startX The X position of the top-left selection point.
+ * @param startY The Y position of the top-left selection point.
+ * @param endX The X position of the bottom-right selection point.
+ * @param endY The Y position of the bottom-right selection point.
+ */
+ private native String nativeGetText(int nativeClass,
+ int startX, int startY, int endX, int endY);
}
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index 60b24bc..be6b4e2 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -33,8 +33,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.widget.ActivityChooserModel.ActivityChooserModelClient;
/**
@@ -366,7 +364,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
getListPopupWindow().dismiss();
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
+ viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
}
return true;
@@ -400,7 +398,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
}
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
+ viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
mIsAttachedToWindow = false;
}
@@ -547,6 +545,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
position = mAdapter.getShowDefaultActivity() ? position : position + 1;
Intent launchIntent = mAdapter.getDataModel().chooseActivity(position);
if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
mContext.startActivity(launchIntent);
}
}
@@ -564,6 +563,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity);
Intent launchIntent = mAdapter.getDataModel().chooseActivity(index);
if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
mContext.startActivity(launchIntent);
}
} else if (view == mExpandActivityOverflowButton) {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index e226d37..bb00049 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -558,7 +558,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
mCurrentWindowEnd = newWindowEnd;
mCurrentWindowStartUnbounded = newWindowStartUnbounded;
if (mRemoteViewsAdapter != null) {
- mRemoteViewsAdapter.setVisibleRangeHint(mCurrentWindowStart, mCurrentWindowEnd);
+ int adapterStart = modulo(mCurrentWindowStart, adapterCount);
+ int adapterEnd = modulo(mCurrentWindowEnd, adapterCount);
+ mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd);
}
}
requestLayout();
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 07523e3..f7a6b27 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -1085,10 +1085,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
for (int i = 0; i < count; i++) {
if (adapter.isEnabled(i)) {
- realCount++;
Object item = adapter.getItem(i);
long id = adapter.getItemId(i);
- completions[i] = new CompletionInfo(id, i, convertSelectionToString(item));
+ completions[realCount] = new CompletionInfo(id, realCount,
+ convertSelectionToString(item));
+ realCount++;
}
}
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index a210f0b..d395fb2 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -55,12 +55,12 @@ import com.android.internal.R;
/**
* A widget that enables the user to select a number form a predefined range.
- * The widget presents an input filed and up and down buttons for selecting the
+ * The widget presents an input field and up and down buttons for selecting the
* current value. Pressing/long pressing the up and down buttons increments and
- * decrements the current value respectively. Touching the input filed shows a
+ * decrements the current value respectively. Touching the input field shows a
* scroll wheel, tapping on which while shown and not moving allows direct edit
* of the current value. Sliding motions up or down hide the buttons and the
- * input filed, show the scroll wheel, and rotate the latter. Flinging is
+ * input field, show the scroll wheel, and rotate the latter. Flinging is
* also supported. The widget enables mapping from positions to strings such
* that instead the position index the corresponding string is displayed.
* <p>
@@ -71,6 +71,11 @@ import com.android.internal.R;
public class NumberPicker extends LinearLayout {
/**
+ * The number of items show in the selector wheel.
+ */
+ public static final int SELECTOR_WHEEL_ITEM_COUNT = 5;
+
+ /**
* The default update interval during long press.
*/
private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;
@@ -1137,14 +1142,17 @@ public class NumberPicker extends LinearLayout {
* items shown on the selector wheel) the selector wheel wrapping is
* enabled.
* </p>
- *
+ * <p>
+ * <strong>Note:</strong> If the number of items, i.e. the range
+ * ({@link #getMaxValue()} - {@link #getMinValue()}) is less than
+ * {@link #SELECTOR_WHEEL_ITEM_COUNT}, the selector wheel will not
+ * wrap. Hence, in such a case calling this method is a NOP.
+ * </p>
* @param wrapSelectorWheel Whether to wrap.
*/
public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
- if (wrapSelectorWheel && (mMaxValue - mMinValue) < mSelectorIndices.length) {
- throw new IllegalStateException("Range less than selector items count.");
- }
- if (wrapSelectorWheel != mWrapSelectorWheel) {
+ final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
+ if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
mWrapSelectorWheel = wrapSelectorWheel;
updateIncrementAndDecrementButtonsVisibilityState();
}
diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index bb27b73..22e9ef1 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -279,6 +279,7 @@ public class ShareActionProvider extends ActionProvider {
final int itemId = item.getItemId();
Intent launchIntent = dataModel.chooseActivity(itemId);
if (launchIntent != null) {
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
mContext.startActivity(launchIntent);
}
return true;
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index a106159..a9aec82 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -102,7 +102,8 @@ public class SpellChecker implements SpellCheckerSessionListener {
mTextServicesManager = (TextServicesManager) mTextView.getContext().
getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
- if (!mTextServicesManager.isSpellCheckerEnabled()) {
+ if (!mTextServicesManager.isSpellCheckerEnabled()
+ || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) {
mSpellCheckerSession = null;
} else {
mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession(
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 02144a8..b82a632 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -342,6 +342,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mTextEditSuggestionItemLayout;
private SuggestionsPopupWindow mSuggestionsPopupWindow;
private SuggestionRangeSpan mSuggestionRangeSpan;
+ private Runnable mShowSuggestionRunnable;
private int mCursorDrawableRes;
private final Drawable[] mCursorDrawable = new Drawable[2];
@@ -357,7 +358,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private float mLastDownPositionX, mLastDownPositionY;
private Callback mCustomSelectionActionModeCallback;
- private final int mSquaredTouchSlopDistance;
// Set when this TextView gained focus with some text selected. Will start selection mode.
private boolean mCreatedWithASelection = false;
@@ -443,15 +443,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
this(context, null);
}
- public TextView(Context context,
- AttributeSet attrs) {
+ public TextView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
@SuppressWarnings("deprecation")
- public TextView(Context context,
- AttributeSet attrs,
- int defStyle) {
+ public TextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mText = "";
@@ -1134,10 +1131,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setLongClickable(longClickable);
prepareCursorControllers();
-
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
- final int touchSlop = viewConfiguration.getScaledTouchSlop();
- mSquaredTouchSlopDistance = touchSlop * touchSlop;
}
private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
@@ -3202,8 +3195,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int n = mFilters.length;
for (int i = 0; i < n; i++) {
- CharSequence out = mFilters[i].filter(text, 0, text.length(),
- EMPTY_SPANNED, 0, 0);
+ CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
if (out != null) {
text = out;
}
@@ -4522,6 +4514,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mSelectionModifierCursorController.onDetached();
}
+ if (mShowSuggestionRunnable != null) {
+ removeCallbacks(mShowSuggestionRunnable);
+ }
+
hideControllers();
resetResolvedDrawables();
@@ -5273,10 +5269,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
state.handleUpEvent(event);
}
if (event.isTracking() && !event.isCanceled()) {
- if (isInSelectionMode) {
- stopSelectionActionMode();
- return true;
- }
+ stopSelectionActionMode();
+ return true;
}
}
}
@@ -5621,11 +5615,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyUp(keyCode, event);
}
- @Override public boolean onCheckIsTextEditor() {
+ @Override
+ public boolean onCheckIsTextEditor() {
return mInputType != EditorInfo.TYPE_NULL;
}
- @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
if (onCheckIsTextEditor() && isEnabled()) {
if (mInputMethodState == null) {
mInputMethodState = new InputMethodState();
@@ -6799,6 +6795,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (changed) mTextDisplayListIsValid = false;
+ }
+
/**
* Returns true if anything changed.
*/
@@ -8339,6 +8341,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
getSelectionController().onTouchEvent(event);
}
+ if (mShowSuggestionRunnable != null) {
+ removeCallbacks(mShowSuggestionRunnable);
+ }
+
if (action == MotionEvent.ACTION_DOWN) {
mLastDownPositionX = event.getX();
mLastDownPositionY = event.getY();
@@ -8362,7 +8368,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
- !shouldIgnoreActionUpEvent() && isFocused();
+ !mIgnoreActionUpEvent && isFocused();
if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
&& mText instanceof Spannable && mLayout != null) {
@@ -8379,7 +8385,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
getSelectionEnd(), ClickableSpan.class);
- if (links.length != 0) {
+ if (links.length > 0) {
links[0].onClick(this);
handled = true;
}
@@ -8402,8 +8408,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (!extractedTextModeWillBeStarted()) {
if (isCursorInsideEasyCorrectionSpan()) {
- showSuggestions();
+ if (mShowSuggestionRunnable == null) {
+ mShowSuggestionRunnable = new Runnable() {
+ public void run() {
+ showSuggestions();
+ }
+ };
+ }
+ postDelayed(mShowSuggestionRunnable,
+ ViewConfiguration.getDoubleTapTimeout());
} else if (hasInsertionController()) {
+ // Move cursor
+ final int offset = getOffsetForPosition(event.getX(), event.getY());
+ Selection.setSelection((Spannable) mText, offset);
getInsertionController().show();
}
}
@@ -8538,17 +8555,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mIgnoreActionUpEvent = true;
}
- /**
- * This method is only valid during a touch event.
- *
- * @return true when the ACTION_UP event should be ignored, false otherwise.
- *
- * @hide
- */
- public boolean shouldIgnoreActionUpEvent() {
- return mIgnoreActionUpEvent;
- }
-
@Override
public boolean onTrackballEvent(MotionEvent event) {
if (mMovement != null && mText instanceof Spannable &&
@@ -8929,14 +8935,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
wordIterator.setCharSequence(mText, minOffset, maxOffset);
selectionStart = wordIterator.getBeginning(minOffset);
- if (selectionStart == BreakIterator.DONE) return false;
-
selectionEnd = wordIterator.getEnd(maxOffset);
- if (selectionEnd == BreakIterator.DONE) return false;
- if (selectionStart == selectionEnd) {
+ if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE ||
+ selectionStart == selectionEnd) {
// Possible when the word iterator does not properly handle the text's language
- long range = getCharRange(selectionStart);
+ long range = getCharRange(minOffset);
selectionStart = extractRangeStartFromLong(range);
selectionEnd = extractRangeEndFromLong(range);
}
@@ -9248,7 +9252,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean vibrate = true;
if (super.performLongClick()) {
- mDiscardNextActionUp = true;
handled = true;
}
@@ -10157,8 +10160,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean willExtract = extractedTextModeWillBeStarted();
- // Do not start the action mode when extracted text will show up full screen, thus
- // immediately hiding the newly created action bar, which would be visually distracting.
+ // Do not start the action mode when extracted text will show up full screen, which would
+ // immediately hide the newly created action bar and would be visually distracting.
if (!willExtract) {
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
mSelectionActionMode = startActionMode(actionModeCallback);
@@ -10184,7 +10187,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
- private void stopSelectionActionMode() {
+ /**
+ * @hide
+ */
+ protected void stopSelectionActionMode() {
if (mSelectionActionMode != null) {
// This will hide the mSelectionModifierCursorController
mSelectionActionMode.finish();
@@ -10798,7 +10804,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final float deltaX = mDownPositionX - ev.getRawX();
final float deltaY = mDownPositionY - ev.getRawY();
final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
- if (distanceSquared < mSquaredTouchSlopDistance) {
+
+ final ViewConfiguration viewConfiguration = ViewConfiguration.get(
+ TextView.this.getContext());
+ final int touchSlop = viewConfiguration.getScaledTouchSlop();
+
+ if (distanceSquared < touchSlop * touchSlop) {
if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) {
// Tapping on the handle dismisses the displayed action popup
mActionPopupWindow.hide();
@@ -11012,7 +11023,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Double tap detection
private long mPreviousTapUpTime = 0;
- private float mPreviousTapPositionX, mPreviousTapPositionY;
+ private float mDownPositionX, mDownPositionY;
+ private boolean mGestureStayedInTapRegion;
SelectionModifierCursorController() {
resetTouchOffsets();
@@ -11075,20 +11087,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y);
// Double tap detection
- long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
- if (duration <= ViewConfiguration.getDoubleTapTimeout() &&
- isPositionOnText(x, y)) {
- final float deltaX = x - mPreviousTapPositionX;
- final float deltaY = y - mPreviousTapPositionY;
- final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
- if (distanceSquared < mSquaredTouchSlopDistance) {
- startSelectionActionMode();
- mDiscardNextActionUp = true;
+ if (mGestureStayedInTapRegion) {
+ long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime;
+ if (duration <= ViewConfiguration.getDoubleTapTimeout()) {
+ final float deltaX = x - mDownPositionX;
+ final float deltaY = y - mDownPositionY;
+ final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
+
+ ViewConfiguration viewConfiguration = ViewConfiguration.get(
+ TextView.this.getContext());
+ int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop();
+ boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop;
+
+ if (stayedInArea && isPositionOnText(x, y)) {
+ startSelectionActionMode();
+ mDiscardNextActionUp = true;
+ }
}
}
- mPreviousTapPositionX = x;
- mPreviousTapPositionY = y;
+ mDownPositionX = x;
+ mDownPositionY = y;
+ mGestureStayedInTapRegion = true;
break;
case MotionEvent.ACTION_POINTER_DOWN:
@@ -11101,6 +11121,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
break;
+ case MotionEvent.ACTION_MOVE:
+ if (mGestureStayedInTapRegion) {
+ final float deltaX = event.getX() - mDownPositionX;
+ final float deltaY = event.getY() - mDownPositionY;
+ final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
+
+ final ViewConfiguration viewConfiguration = ViewConfiguration.get(
+ TextView.this.getContext());
+ int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop();
+
+ if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) {
+ mGestureStayedInTapRegion = false;
+ }
+ }
+ break;
+
case MotionEvent.ACTION_UP:
mPreviousTapUpTime = SystemClock.uptimeMillis();
break;
diff --git a/core/java/com/android/internal/backup/BackupConstants.java b/core/java/com/android/internal/backup/BackupConstants.java
index 906b5d5..4c276b7 100644
--- a/core/java/com/android/internal/backup/BackupConstants.java
+++ b/core/java/com/android/internal/backup/BackupConstants.java
@@ -24,4 +24,5 @@ public class BackupConstants {
public static final int TRANSPORT_ERROR = 1;
public static final int TRANSPORT_NOT_INITIALIZED = 2;
public static final int AGENT_ERROR = 3;
+ public static final int AGENT_UNKNOWN = 4;
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9c45dc6..6a99a2b 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -243,7 +243,7 @@ public class ZygoteInit {
private static void preloadClasses() {
final VMRuntime runtime = VMRuntime.getRuntime();
- InputStream is = ZygoteInit.class.getClassLoader().getResourceAsStream(
+ InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(
PRELOADED_CLASSES);
if (is == null) {
Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index edeb2a8..d1aa1ce 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -142,6 +142,14 @@ public class ArrayUtils
return false;
}
+ public static long total(long[] array) {
+ long total = 0;
+ for (long value : array) {
+ total += value;
+ }
+ return total;
+ }
+
/**
* Appends an element to a copy of the array and returns the copy.
* @param array The original array, or null to represent an empty array.
diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java
index 3ce95e7..8a8f315 100644
--- a/core/java/com/android/internal/util/FileRotator.java
+++ b/core/java/com/android/internal/util/FileRotator.java
@@ -17,9 +17,9 @@
package com.android.internal.util;
import android.os.FileUtils;
+import android.util.Slog;
-import com.android.internal.util.FileRotator.Reader;
-import com.android.internal.util.FileRotator.Writer;
+import com.android.internal.util.FileRotator.Rewriter;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -41,12 +41,15 @@ import libcore.io.IoUtils;
* Instead of manipulating files directly, users implement interfaces that
* perform operations on {@link InputStream} and {@link OutputStream}. This
* enables atomic rewriting of file contents in
- * {@link #combineActive(Reader, Writer, long)}.
+ * {@link #rewriteActive(Rewriter, long)}.
* <p>
* Users must periodically call {@link #maybeRotate(long)} to perform actual
* rotation. Not inherently thread safe.
*/
public class FileRotator {
+ private static final String TAG = "FileRotator";
+ private static final boolean LOGD = true;
+
private final File mBasePath;
private final String mPrefix;
private final long mRotateAgeMillis;
@@ -73,6 +76,15 @@ public class FileRotator {
}
/**
+ * External class that reads existing data from given {@link InputStream},
+ * then writes any modified data to {@link OutputStream}.
+ */
+ public interface Rewriter extends Reader, Writer {
+ public void reset();
+ public boolean shouldWrite();
+ }
+
+ /**
* Create a file rotator.
*
* @param basePath Directory under which all files will be placed.
@@ -96,6 +108,8 @@ public class FileRotator {
if (!name.startsWith(mPrefix)) continue;
if (name.endsWith(SUFFIX_BACKUP)) {
+ if (LOGD) Slog.d(TAG, "recovering " + name);
+
final File backupFile = new File(mBasePath, name);
final File file = new File(
mBasePath, name.substring(0, name.length() - SUFFIX_BACKUP.length()));
@@ -104,6 +118,8 @@ public class FileRotator {
backupFile.renameTo(file);
} else if (name.endsWith(SUFFIX_NO_BACKUP)) {
+ if (LOGD) Slog.d(TAG, "recovering " + name);
+
final File noBackupFile = new File(mBasePath, name);
final File file = new File(
mBasePath, name.substring(0, name.length() - SUFFIX_NO_BACKUP.length()));
@@ -116,26 +132,95 @@ public class FileRotator {
}
/**
- * Atomically combine data with existing data in currently active file.
- * Maintains a backup during write, which is restored if the write fails.
+ * Delete all files managed by this rotator.
*/
- public void combineActive(Reader reader, Writer writer, long currentTimeMillis)
+ public void deleteAll() {
+ final FileInfo info = new FileInfo(mPrefix);
+ for (String name : mBasePath.list()) {
+ if (!info.parse(name)) continue;
+
+ // delete each file that matches parser
+ new File(mBasePath, name).delete();
+ }
+ }
+
+ /**
+ * Process currently active file, first reading any existing data, then
+ * writing modified data. Maintains a backup during write, which is restored
+ * if the write fails.
+ */
+ public void rewriteActive(Rewriter rewriter, long currentTimeMillis)
throws IOException {
final String activeName = getActiveName(currentTimeMillis);
+ rewriteSingle(rewriter, activeName);
+ }
- final File file = new File(mBasePath, activeName);
+ @Deprecated
+ public void combineActive(final Reader reader, final Writer writer, long currentTimeMillis)
+ throws IOException {
+ rewriteActive(new Rewriter() {
+ /** {@inheritDoc} */
+ public void reset() {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void read(InputStream in) throws IOException {
+ reader.read(in);
+ }
+
+ /** {@inheritDoc} */
+ public boolean shouldWrite() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public void write(OutputStream out) throws IOException {
+ writer.write(out);
+ }
+ }, currentTimeMillis);
+ }
+
+ /**
+ * Process all files managed by this rotator, usually to rewrite historical
+ * data. Each file is processed atomically.
+ */
+ public void rewriteAll(Rewriter rewriter) throws IOException {
+ final FileInfo info = new FileInfo(mPrefix);
+ for (String name : mBasePath.list()) {
+ if (!info.parse(name)) continue;
+
+ // process each file that matches parser
+ rewriteSingle(rewriter, name);
+ }
+ }
+
+ /**
+ * Process a single file atomically, first reading any existing data, then
+ * writing modified data. Maintains a backup during write, which is restored
+ * if the write fails.
+ */
+ private void rewriteSingle(Rewriter rewriter, String name) throws IOException {
+ if (LOGD) Slog.d(TAG, "rewriting " + name);
+
+ final File file = new File(mBasePath, name);
final File backupFile;
+ rewriter.reset();
+
if (file.exists()) {
// read existing data
- readFile(file, reader);
+ readFile(file, rewriter);
+
+ // skip when rewriter has nothing to write
+ if (!rewriter.shouldWrite()) return;
// backup existing data during write
- backupFile = new File(mBasePath, activeName + SUFFIX_BACKUP);
+ backupFile = new File(mBasePath, name + SUFFIX_BACKUP);
file.renameTo(backupFile);
try {
- writeFile(file, writer);
+ writeFile(file, rewriter);
// write success, delete backup
backupFile.delete();
@@ -148,11 +233,11 @@ public class FileRotator {
} else {
// create empty backup during write
- backupFile = new File(mBasePath, activeName + SUFFIX_NO_BACKUP);
+ backupFile = new File(mBasePath, name + SUFFIX_NO_BACKUP);
backupFile.createNewFile();
try {
- writeFile(file, writer);
+ writeFile(file, rewriter);
// write success, delete empty backup
backupFile.delete();
@@ -176,6 +261,8 @@ public class FileRotator {
// read file when it overlaps
if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) {
+ if (LOGD) Slog.d(TAG, "reading matching " + name);
+
final File file = new File(mBasePath, name);
readFile(file, reader);
}
@@ -224,16 +311,20 @@ public class FileRotator {
if (!info.parse(name)) continue;
if (info.isActive()) {
- // found active file; rotate if old enough
- if (info.startMillis < rotateBefore) {
+ if (info.startMillis <= rotateBefore) {
+ // found active file; rotate if old enough
+ if (LOGD) Slog.d(TAG, "rotating " + name);
+
info.endMillis = currentTimeMillis;
final File file = new File(mBasePath, name);
final File destFile = new File(mBasePath, info.build());
file.renameTo(destFile);
}
- } else if (info.endMillis < deleteBefore) {
+ } else if (info.endMillis <= deleteBefore) {
// found rotated file; delete if old enough
+ if (LOGD) Slog.d(TAG, "deleting " + name);
+
final File file = new File(mBasePath, name);
file.delete();
}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
new file mode 100644
index 0000000..3dd2284
--- /dev/null
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 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.internal.util;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * Lightweight wrapper around {@link PrintWriter} that automatically indents
+ * newlines based on internal state. Delays writing indent until first actual
+ * write on a newline, enabling indent modification after newline.
+ */
+public class IndentingPrintWriter extends PrintWriter {
+ private final String mIndent;
+
+ private StringBuilder mBuilder = new StringBuilder();
+ private String mCurrent = new String();
+ private boolean mEmptyLine = true;
+
+ public IndentingPrintWriter(Writer writer, String indent) {
+ super(writer);
+ mIndent = indent;
+ }
+
+ public void increaseIndent() {
+ mBuilder.append(mIndent);
+ mCurrent = mBuilder.toString();
+ }
+
+ public void decreaseIndent() {
+ mBuilder.delete(0, mIndent.length());
+ mCurrent = mBuilder.toString();
+ }
+
+ @Override
+ public void println() {
+ super.println();
+ mEmptyLine = true;
+ }
+
+ @Override
+ public void write(char[] buf, int offset, int count) {
+ if (mEmptyLine) {
+ mEmptyLine = false;
+ super.print(mCurrent);
+ }
+ super.write(buf, offset, count);
+ }
+}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index a235d9a..9024d8d 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -387,9 +387,9 @@ public class InputConnectionWrapper implements InputConnection {
}
}
- public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ public boolean deleteSurroundingText(int beforeLength, int afterLength) {
try {
- mIInputContext.deleteSurroundingText(leftLength, rightLength);
+ mIInputContext.deleteSurroundingText(beforeLength, afterLength);
return true;
} catch (RemoteException e) {
return false;
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 32e733b..9579bce 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -35,6 +35,11 @@ public class EditableInputConnection extends BaseInputConnection {
private final TextView mTextView;
+ // Keeps track of nested begin/end batch edit to ensure this connection always has a
+ // balanced impact on its associated TextView.
+ // A negative value means that this connection has been finished by the InputMethodManager.
+ private int mBatchEditNesting;
+
public EditableInputConnection(TextView textview) {
super(textview, true);
mTextView = textview;
@@ -48,19 +53,35 @@ public class EditableInputConnection extends BaseInputConnection {
}
return null;
}
-
+
@Override
public boolean beginBatchEdit() {
- mTextView.beginBatchEdit();
- return true;
+ synchronized(this) {
+ if (mBatchEditNesting >= 0) {
+ mTextView.beginBatchEdit();
+ mBatchEditNesting++;
+ return true;
+ }
+ }
+ return false;
}
-
+
@Override
public boolean endBatchEdit() {
- mTextView.endBatchEdit();
- return true;
+ synchronized(this) {
+ if (mBatchEditNesting > 0) {
+ // When the connection is reset by the InputMethodManager and finishComposingText
+ // is called, some endBatchEdit calls may still be asynchronously received from the
+ // IME. Do not take these into account, thus ensuring that this IC's final
+ // contribution to mTextView's nested batch edit count is zero.
+ mTextView.endBatchEdit();
+ mBatchEditNesting--;
+ return true;
+ }
+ }
+ return false;
}
-
+
@Override
public boolean clearMetaKeyStates(int states) {
final Editable content = getEditable();
@@ -76,7 +97,24 @@ public class EditableInputConnection extends BaseInputConnection {
}
return true;
}
-
+
+ @Override
+ public boolean finishComposingText() {
+ final boolean superResult = super.finishComposingText();
+ synchronized(this) {
+ if (mBatchEditNesting < 0) {
+ // The connection was already finished
+ return false;
+ }
+ while (mBatchEditNesting > 0) {
+ endBatchEdit();
+ }
+ // Will prevent any further calls to begin or endBatchEdit
+ mBatchEditNesting = -1;
+ }
+ return superResult;
+ }
+
@Override
public boolean commitCompletion(CompletionInfo text) {
if (DEBUG) Log.v(TAG, "commitCompletion " + text);