Merge "Use audio_source_t consistently"
diff --git a/Android.mk b/Android.mk
index 83c4b5b..42cb097 100644
--- a/Android.mk
+++ b/Android.mk
@@ -449,8 +449,6 @@
resources/samples/training/ads-and-ux "Mobile Advertisement Integration" \
-samplecode $(sample_dir)/MultiResolution \
resources/samples/MultiResolution "Multiple Resolutions" \
- -samplecode $(sample_dir)/NFCDemo \
- resources/samples/NFCDemo "NFC Demo" \
-samplecode $(sample_dir)/training/multiscreen/newsreader \
resources/samples/newsreader "News Reader" \
-samplecode $(sample_dir)/NotePad \
diff --git a/api/current.txt b/api/current.txt
index 7c05ed6..4a8cd3c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -436,7 +436,7 @@
field public static final int fadeEnabled = 16843390; // 0x101027e
field public static final int fadeOffset = 16843383; // 0x1010277
field public static final int fadeScrollbars = 16843434; // 0x10102aa
- field public static final deprecated int fadingEdge = 16842975; // 0x10100df
+ field public static final int fadingEdge = 16842975; // 0x10100df
field public static final int fadingEdgeLength = 16842976; // 0x10100e0
field public static final int fastScrollAlwaysVisible = 16843573; // 0x1010335
field public static final int fastScrollEnabled = 16843302; // 0x1010226
@@ -5351,6 +5351,7 @@
method public static android.content.Intent makeMainActivity(android.content.ComponentName);
method public static android.content.Intent makeMainSelectorActivity(java.lang.String, java.lang.String);
method public static android.content.Intent makeRestartActivityTask(android.content.ComponentName);
+ method public static java.lang.String normalizeMimeType(java.lang.String);
method public static android.content.Intent parseIntent(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static android.content.Intent parseUri(java.lang.String, int) throws java.net.URISyntaxException;
method public android.content.Intent putCharSequenceArrayListExtra(java.lang.String, java.util.ArrayList<java.lang.CharSequence>);
@@ -5399,13 +5400,16 @@
method public android.content.Intent setClassName(java.lang.String, java.lang.String);
method public android.content.Intent setComponent(android.content.ComponentName);
method public android.content.Intent setData(android.net.Uri);
+ method public android.content.Intent setDataAndNormalize(android.net.Uri);
method public android.content.Intent setDataAndType(android.net.Uri, java.lang.String);
+ method public android.content.Intent setDataAndTypeAndNormalize(android.net.Uri, java.lang.String);
method public void setExtrasClassLoader(java.lang.ClassLoader);
method public android.content.Intent setFlags(int);
method public android.content.Intent setPackage(java.lang.String);
method public void setSelector(android.content.Intent);
method public void setSourceBounds(android.graphics.Rect);
method public android.content.Intent setType(java.lang.String);
+ method public android.content.Intent setTypeAndNormalize(java.lang.String);
method public deprecated java.lang.String toURI();
method public java.lang.String toUri(int);
method public void writeToParcel(android.os.Parcel, int);
@@ -6791,16 +6795,19 @@
public class ContentObservable extends android.database.Observable {
ctor public ContentObservable();
- method public void dispatchChange(boolean);
- method public void notifyChange(boolean);
+ method public deprecated void dispatchChange(boolean);
+ method public void dispatchChange(boolean, android.net.Uri);
+ method public deprecated void notifyChange(boolean);
method public void registerObserver(android.database.ContentObserver);
}
public abstract class ContentObserver {
ctor public ContentObserver(android.os.Handler);
method public boolean deliverSelfNotifications();
- method public final void dispatchChange(boolean);
+ method public final deprecated void dispatchChange(boolean);
+ method public final void dispatchChange(boolean, android.net.Uri);
method public void onChange(boolean);
+ method public void onChange(boolean, android.net.Uri);
}
public abstract interface CrossProcessCursor implements android.database.Cursor {
@@ -6819,7 +6826,7 @@
public abstract interface Cursor {
method public abstract void close();
method public abstract void copyStringToBuffer(int, android.database.CharArrayBuffer);
- method public abstract void deactivate();
+ method public abstract deprecated void deactivate();
method public abstract byte[] getBlob(int);
method public abstract int getColumnCount();
method public abstract int getColumnIndex(java.lang.String);
@@ -7785,7 +7792,7 @@
package android.graphics {
- public class AvoidXfermode extends android.graphics.Xfermode {
+ public deprecated class AvoidXfermode extends android.graphics.Xfermode {
ctor public AvoidXfermode(int, int, android.graphics.AvoidXfermode.Mode);
}
@@ -8564,7 +8571,7 @@
field public int bytesPerPixel;
}
- public class PixelXorXfermode extends android.graphics.Xfermode {
+ public deprecated class PixelXorXfermode extends android.graphics.Xfermode {
ctor public PixelXorXfermode(int);
}
@@ -11831,6 +11838,7 @@
method public abstract boolean isHierarchical();
method public boolean isOpaque();
method public abstract boolean isRelative();
+ method public android.net.Uri normalize();
method public static android.net.Uri parse(java.lang.String);
method public abstract java.lang.String toString();
method public static android.net.Uri withAppendedPath(android.net.Uri, java.lang.String);
@@ -12633,6 +12641,8 @@
method public short getTnf();
method public byte[] getType();
method public deprecated byte[] toByteArray();
+ method public java.lang.String toMimeType();
+ method public android.net.Uri toUri();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
field public static final byte[] RTD_ALTERNATIVE_CARRIER;
@@ -14424,6 +14434,7 @@
field public static final int HONEYCOMB_MR2 = 13; // 0xd
field public static final int ICE_CREAM_SANDWICH = 14; // 0xe
field public static final int ICE_CREAM_SANDWICH_MR1 = 15; // 0xf
+ field public static final int JELLY_BEAN = 10000; // 0x2710
}
public final class Bundle implements java.lang.Cloneable android.os.Parcelable {
@@ -17560,6 +17571,7 @@
field public static final java.lang.String RADIO_NFC = "nfc";
field public static final java.lang.String RADIO_WIFI = "wifi";
field public static final java.lang.String RINGTONE = "ringtone";
+ field public static final java.lang.String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj";
field public static final java.lang.String SCREEN_BRIGHTNESS = "screen_brightness";
field public static final java.lang.String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
field public static final int SCREEN_BRIGHTNESS_MODE_AUTOMATIC = 1; // 0x1
@@ -21387,6 +21399,7 @@
field public static final int DENSITY_MEDIUM = 160; // 0xa0
field public static final int DENSITY_TV = 213; // 0xd5
field public static final int DENSITY_XHIGH = 320; // 0x140
+ field public static final int DENSITY_XXHIGH = 480; // 0x1e0
field public float density;
field public int densityDpi;
field public int heightPixels;
@@ -23817,8 +23830,9 @@
method public final void dispatchOnGlobalLayout();
method public final boolean dispatchOnPreDraw();
method public boolean isAlive();
- method public void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
+ method public deprecated void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
method public void removeOnGlobalFocusChangeListener(android.view.ViewTreeObserver.OnGlobalFocusChangeListener);
+ method public void removeOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
method public void removeOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
method public void removeOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
@@ -26671,6 +26685,7 @@
method public void setOnValueChangedListener(android.widget.NumberPicker.OnValueChangeListener);
method public void setValue(int);
method public void setWrapSelectorWheel(boolean);
+ field public static final int SELECTOR_WHEEL_ITEM_COUNT = 5; // 0x5
}
public static abstract interface NumberPicker.Formatter {
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp
index 613435d..7703058 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -38,7 +38,7 @@
static const int32_t kAudioBitRate = 12200;
static const int64_t kDurationUs = 10000000LL; // 10 seconds
-#if 1
+#if 0
class DummySource : public MediaSource {
public:
@@ -318,7 +318,7 @@
sp<MetaData> encMeta = new MetaData;
encMeta->setCString(kKeyMIMEType,
- 1 ? MEDIA_MIMETYPE_AUDIO_AMR_WB : MEDIA_MIMETYPE_AUDIO_AAC);
+ 0 ? MEDIA_MIMETYPE_AUDIO_AMR_WB : MEDIA_MIMETYPE_AUDIO_AAC);
encMeta->setInt32(kKeySampleRate, kSampleRate);
encMeta->setInt32(kKeyChannelCount, kNumChannels);
encMeta->setInt32(kKeyMaxInputSize, 8192);
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.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 @@
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 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 @@
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 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 @@
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 455d2f0..9807b89 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3741,7 +3741,6 @@
}
final void handleTrimMemory(int level) {
- WindowManagerImpl.getDefault().trimMemory(level);
ArrayList<ComponentCallbacks2> callbacks;
synchronized (mPackages) {
@@ -3752,6 +3751,7 @@
for (int i=0; i<N; i++) {
callbacks.get(i).onTrimMemory(level);
}
+ WindowManagerImpl.getDefault().trimMemory(level);
}
private void setupGraphicsSupport(LoadedApk info) {
@@ -3804,7 +3804,7 @@
// 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/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 8fa95b4..d04e9db 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -551,7 +551,6 @@
try {
// If the intent was created from a suggestion, it will always have an explicit
// component here.
- Log.i(LOG_TAG, "Starting (as ourselves) " + intent.toUri(0));
getContext().startActivity(intent);
// If the search switches to a different activity,
// SearchDialogWrapper#performActivityResuming
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 @@
* 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 @@
* 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 @@
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 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 @@
}
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 @@
// 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 @@
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.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Locale;
import java.util.Set;
/**
@@ -4420,22 +4421,24 @@
/**
* 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 @@
}
/**
- * 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 @@
* 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 @@
}
/**
- * (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.
+ * Normalize and set an explicit MIME data type.
*
- * <p><em>Note: MIME type, Uri scheme, and host name 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>
+ * <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.
*
- * @param data The URI of the data this intent is now targeting.
+ * <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 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,
+ * 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 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 #normalizeMimeType
+ * @see android.net.Uri#normalize
+ * @see #setDataAndTypeAndNormalize
*/
public Intent setDataAndType(Uri data, String type) {
mData = data;
@@ -4499,6 +4564,35 @@
}
/**
+ * (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 @@
*
* <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 @@
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 22df272..b28ed8d 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -300,7 +300,7 @@
*/
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 @@
* {@hide}
*/
public IContentObserver getContentObserver() {
- synchronized(lock) {
+ synchronized (mLock) {
if (mTransport == null) {
mTransport = new Transport(this);
}
@@ -101,8 +59,8 @@
* {@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 @@
}
/**
- * 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 @@
* 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 aa0f61e37..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 @@
}
@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 @@
}
/**
- * 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 @@
* 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 @@
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 @@
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/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 946300f..82bb23e 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -65,8 +65,7 @@
* 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 @@
* 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.
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 @@
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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* @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 @@
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 @@
* 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 @@
* 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 @@
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 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.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 @@
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 @@
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 @@
txPackets = readLongArray(in);
operations = readLongArray(in);
bucketCount = bucketStart.length;
+ totalBytes = in.readLong();
}
/** {@inheritDoc} */
@@ -130,6 +140,7 @@
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 @@
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 @@
txPackets = readVarLongArray(in);
operations = readVarLongArray(in);
bucketCount = bucketStart.length;
+ totalBytes = total(rxBytes) + total(txBytes);
break;
}
default: {
@@ -208,6 +221,13 @@
}
/**
+ * 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 @@
* 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 @@
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 @@
/**
* Remove buckets older than requested cutoff.
*/
+ @Deprecated
public void removeBucketsBefore(long cutoff) {
int i;
for (i = 0; i < bucketCount; i++) {
@@ -415,6 +441,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* 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 @@
// 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.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 @@
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 @@
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/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 @@
* @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 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 @@
/**
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
/**
* Represents an immutable NDEF Record.
@@ -305,9 +307,9 @@
* @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 @@
* 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 @@
}
/**
+ * 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");
- return new NdefRecord(TNF_MIME_MEDIA, mimeType.getBytes(Charsets.US_ASCII), null,
- mimeData);
+ // 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
+
+ // 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 @@
* 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 @@
}
/**
- * 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);
}
-
- /*
- * 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)");
- }
- 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));
- }
-
- private static byte[] concat(byte[]... arrays) {
- int length = 0;
- for (byte[] array : arrays) {
- length += array.length;
- }
- byte[] result = new byte[length];
- int pos = 0;
- for (byte[] array : arrays) {
- System.arraycopy(array, 0, result, pos, array.length);
- pos += array.length;
- }
- return result;
+ return null;
}
/**
- * Main parsing method.<p>
+ * 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));
+ }
+ return null;
+ }
+
+ /**
+ * 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;
+ }
+
+ // 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;
+ }
+ 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 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 @@
}
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 @@
* <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 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 @@
*/
@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 @@
* @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 @@
}
/**
+ * 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 @@
* 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 @@
/**
* 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 @@
* <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 @@
* 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 @@
* <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 @@
// 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 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 @@
SCREEN_OFF_TIMEOUT,
SCREEN_BRIGHTNESS,
SCREEN_BRIGHTNESS_MODE,
+ SCREEN_AUTO_BRIGHTNESS_ADJ,
VIBRATE_ON,
MODE_RINGER,
MODE_RINGER_STREAMS_AFFECTED,
@@ -4104,17 +4111,38 @@
/** {@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_NETWORK_BUCKET_DURATION = "netstats_network_bucket_duration";
+ public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes";
/** {@hide} */
- public static final String NETSTATS_NETWORK_MAX_HISTORY = "netstats_network_max_history";
+ 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_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_TAG_MAX_HISTORY = "netstats_tag_max_history";
+ 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_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/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 @@
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/format/Time.java b/core/java/android/text/format/Time.java
index b4445ca..e9b0d32 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -481,6 +481,9 @@
* @throws android.util.TimeFormatException if s cannot be parsed.
*/
public boolean parse3339(String s) {
+ if (s == null) {
+ throw new NullPointerException("time string is null");
+ }
if (nativeParse3339(s)) {
timezone = TIMEZONE_UTC;
return true;
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 0f26a34..5dc206f 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -25,6 +25,7 @@
import android.text.ParcelableSpan;
import android.text.TextPaint;
import android.text.TextUtils;
+import android.util.Log;
import android.widget.TextView;
import java.util.Arrays;
@@ -114,7 +115,7 @@
* @param context Context for the application
* @param locale locale Locale of the suggestions
* @param suggestions Suggestions for the string under the span. Only the first up to
- * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered.
+ * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. Null values not permitted.
* @param flags Additional flags indicating how this span is handled in TextView
* @param notificationTargetClass if not null, this class will get notified when the user
* selects one of the suggestions.
@@ -124,10 +125,13 @@
final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length);
mSuggestions = Arrays.copyOf(suggestions, N);
mFlags = flags;
- if (context != null && locale == null) {
+ if (locale != null) {
+ mLocaleString = locale.toString();
+ } else if (context != null) {
mLocaleString = context.getResources().getConfiguration().locale.toString();
} else {
- mLocaleString = locale.toString();
+ Log.e("SuggestionSpan", "No locale or context specified in SuggestionSpan constructor");
+ mLocaleString = "";
}
if (notificationTargetClass != null) {
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 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/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index e06d661..c08a4024 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -22,6 +22,7 @@
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 @@
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 @@
@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;
@@ -968,6 +979,7 @@
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) {
@@ -985,6 +997,7 @@
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 @@
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 @@
}
void onPreDraw(Rect dirty) {
+
}
void onPostDraw() {
@@ -832,6 +842,8 @@
dirty = null;
}
+ beginFrame();
+
onPreDraw(dirty);
HardwareCanvas canvas = mCanvas;
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 @@
}
/**
- * @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 @@
}
/**
- * @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/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 5c63366..dda695fc 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2661,8 +2661,7 @@
child.onAnimationStart();
}
- more = a.getTransformation(drawingTime, mChildTransformation,
- scalingRequired ? mAttachInfo.mApplicationScale : 1f);
+ more = a.getTransformation(drawingTime, mChildTransformation, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (mInvalidationTransformation == null) {
mInvalidationTransformation = new Transformation();
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 @@
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 @@
* @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/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 @@
/**
* 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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
ic.finishComposingText();
}
}
-
+
public void displayCompletions(View view, CompletionInfo[] completions) {
checkFocus();
synchronized (mH) {
@@ -831,7 +837,7 @@
* 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 @@
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 @@
// 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 @@
}
}
- 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/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 @@
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 @@
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 @@
@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 @@
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 69c15a6..b255c57 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -20,6 +20,7 @@
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.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 @@
}
/**
- * 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);
}
- private void sendKeyPress(int keyCode) {
- long eventTime = SystemClock.uptimeMillis();
- sendKeyEvent(new KeyEvent(eventTime, eventTime,
- KeyEvent.ACTION_DOWN, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD));
- sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
- KeyEvent.ACTION_UP, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD));
+ @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) {
- // 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);
- }
+ 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);
+ }
+ }
+ }
+
+ /**
+ * 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, KeyEvent.KEYCODE_DEL, 0, 0,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD));
+ sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL, 0, 0,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD));
+ }
}
@@ -797,6 +889,10 @@
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;
@@ -4520,6 +4616,11 @@
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;
}
@@ -4943,12 +5044,18 @@
@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;
}
@@ -5729,12 +5836,49 @@
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() {
@@ -8914,6 +9058,35 @@
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;
@@ -9039,10 +9212,13 @@
*/
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);
+ }
}
}
@@ -9592,6 +9768,18 @@
}
/**
+ * 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
@@ -9677,6 +9865,23 @@
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();
@@ -9722,6 +9927,7 @@
/* 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 c4981e1..fe51581 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -647,18 +647,6 @@
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
@@ -1126,6 +1114,11 @@
// 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
@@ -1571,11 +1564,6 @@
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;
@@ -1737,6 +1725,28 @@
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;
}
}
};
@@ -2712,6 +2722,15 @@
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,
@@ -2976,4 +2995,35 @@
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.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 @@
getListPopupWindow().dismiss();
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
+ viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
}
return true;
@@ -400,7 +398,7 @@
}
ViewTreeObserver viewTreeObserver = getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
- viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener);
+ viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener);
}
mIsAttachedToWindow = false;
}
@@ -547,6 +545,7 @@
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 @@
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 @@
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/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 @@
/**
* 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 @@
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 @@
* 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 @@
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..570f0f9 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -102,7 +102,8 @@
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(
@@ -341,56 +342,15 @@
final int end = editable.getSpanEnd(spellCheckSpan);
if (start < 0 || end <= start) return; // span was removed in the meantime
- // Other suggestion spans may exist on that region, with identical suggestions, filter
- // them out to avoid duplicates.
- SuggestionSpan[] suggestionSpans = editable.getSpans(start, end, SuggestionSpan.class);
- final int length = suggestionSpans.length;
- for (int i = 0; i < length; i++) {
- final int spanStart = editable.getSpanStart(suggestionSpans[i]);
- final int spanEnd = editable.getSpanEnd(suggestionSpans[i]);
- if (spanStart != start || spanEnd != end) {
- // Nulled (to avoid new array allocation) if not on that exact same region
- suggestionSpans[i] = null;
- }
- }
-
final int suggestionsCount = suggestionsInfo.getSuggestionsCount();
- String[] suggestions;
if (suggestionsCount <= 0) {
// A negative suggestion count is possible
- suggestions = ArrayUtils.emptyArray(String.class);
- } else {
- int numberOfSuggestions = 0;
- suggestions = new String[suggestionsCount];
+ return;
+ }
- for (int i = 0; i < suggestionsCount; i++) {
- final String spellSuggestion = suggestionsInfo.getSuggestionAt(i);
- if (spellSuggestion == null) break;
- boolean suggestionFound = false;
-
- for (int j = 0; j < length && !suggestionFound; j++) {
- if (suggestionSpans[j] == null) break;
-
- String[] suggests = suggestionSpans[j].getSuggestions();
- for (int k = 0; k < suggests.length; k++) {
- if (spellSuggestion.equals(suggests[k])) {
- // The suggestion is already provided by an other SuggestionSpan
- suggestionFound = true;
- break;
- }
- }
- }
-
- if (!suggestionFound) {
- suggestions[numberOfSuggestions++] = spellSuggestion;
- }
- }
-
- if (numberOfSuggestions != suggestionsCount) {
- String[] newSuggestions = new String[numberOfSuggestions];
- System.arraycopy(suggestions, 0, newSuggestions, 0, numberOfSuggestions);
- suggestions = newSuggestions;
- }
+ String[] suggestions = new String[suggestionsCount];
+ for (int i = 0; i < suggestionsCount; i++) {
+ suggestions[i] = suggestionsInfo.getSuggestionAt(i);
}
SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f78c247..39a4f42 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -342,6 +342,7 @@
private int mTextEditSuggestionItemLayout;
private SuggestionsPopupWindow mSuggestionsPopupWindow;
private SuggestionRangeSpan mSuggestionRangeSpan;
+ private Runnable mShowSuggestionRunnable;
private int mCursorDrawableRes;
private final Drawable[] mCursorDrawable = new Drawable[2];
@@ -4513,6 +4514,10 @@
mSelectionModifierCursorController.onDetached();
}
+ if (mShowSuggestionRunnable != null) {
+ removeCallbacks(mShowSuggestionRunnable);
+ }
+
hideControllers();
resetResolvedDrawables();
@@ -6790,6 +6795,12 @@
}
}
+ @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.
*/
@@ -8330,6 +8341,10 @@
getSelectionController().onTouchEvent(event);
}
+ if (mShowSuggestionRunnable != null) {
+ removeCallbacks(mShowSuggestionRunnable);
+ }
+
if (action == MotionEvent.ACTION_DOWN) {
mLastDownPositionX = event.getX();
mLastDownPositionY = event.getY();
@@ -8370,17 +8385,13 @@
ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
getSelectionEnd(), ClickableSpan.class);
- if (links.length != 0) {
+ if (links.length > 0) {
links[0].onClick(this);
handled = true;
}
}
if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
- // Move cursor
- final int offset = getOffsetForPosition(event.getX(), event.getY());
- Selection.setSelection((Spannable) mText, offset);
-
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
@@ -8397,8 +8408,19 @@
}
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();
}
}
@@ -9822,17 +9844,34 @@
String[] suggestions = suggestionSpan.getSuggestions();
int nbSuggestions = suggestions.length;
for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
- SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
- suggestionInfo.suggestionSpan = suggestionSpan;
- suggestionInfo.suggestionIndex = suggestionIndex;
- suggestionInfo.text.replace(0, suggestionInfo.text.length(),
- suggestions[suggestionIndex]);
+ String suggestion = suggestions[suggestionIndex];
- mNumberOfSuggestions++;
- if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
- // Also end outer for loop
- spanIndex = nbSpans;
- break;
+ boolean suggestionIsDuplicate = false;
+ for (int i = 0; i < mNumberOfSuggestions; i++) {
+ if (mSuggestionInfos[i].text.toString().equals(suggestion)) {
+ SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan;
+ final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan);
+ final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan);
+ if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) {
+ suggestionIsDuplicate = true;
+ break;
+ }
+ }
+ }
+
+ if (!suggestionIsDuplicate) {
+ SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
+ suggestionInfo.suggestionSpan = suggestionSpan;
+ suggestionInfo.suggestionIndex = suggestionIndex;
+ suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion);
+
+ mNumberOfSuggestions++;
+
+ if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
+ // Also end outer for loop
+ spanIndex = nbSpans;
+ break;
+ }
}
}
}
@@ -9841,7 +9880,7 @@
highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd);
}
- // Add to dictionary item if there is a span with the misspelled flag
+ // Add "Add to dictionary" item if there is a span with the misspelled flag
if (misspelledSpan != null) {
final int misspelledStart = spannable.getSpanStart(misspelledSpan);
final int misspelledEnd = spannable.getSpanEnd(misspelledSpan);
@@ -9899,8 +9938,9 @@
suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// Add the text before and after the span.
- suggestionInfo.text.insert(0, mText.toString().substring(unionStart, spanStart));
- suggestionInfo.text.append(mText.toString().substring(spanEnd, unionEnd));
+ final String textAsString = text.toString();
+ suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart));
+ suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd));
}
@Override
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 @@
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 @@
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 @@
* 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 @@
}
/**
+ * 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 @@
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 @@
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 @@
}
/**
- * 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 @@
} 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 @@
// 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 @@
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 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 @@
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 @@
}
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 @@
}
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);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 905a171..6893ffb 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -404,7 +404,7 @@
saveLockPassword(null, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
setLockPatternEnabled(false);
saveLockPattern(null);
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
}
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 3e9ab86..c8b725a 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -776,9 +776,6 @@
static void doDrawGlyphs(SkCanvas* canvas, const jchar* glyphArray, int index, int count,
jfloat x, jfloat y, int flags, SkPaint* paint) {
- // TODO: need to suppress this code after the GL renderer is modified for not
- // copying the paint
-
// Beware: this needs Glyph encoding (already done on the Paint constructor)
canvas->drawText(glyphArray + index * 2, count * 2, x, y, *paint);
}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 9f3238ab..9bcfa5f 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -466,7 +466,8 @@
jchar* glyphsArray = env->GetCharArrayElements(glyphs, NULL);
TextLayoutCacheValue value(contextCount);
- TextLayoutEngine::getInstance().computeValues(&value, paint, text, start, count, contextCount, flags);
+ TextLayoutEngine::getInstance().computeValues(&value, paint, text, start, count,
+ contextCount, flags);
const jchar* shapedGlyphs = value.getGlyphs();
size_t glyphsCount = value.getGlyphsCount();
memcpy(glyphsArray, shapedGlyphs, sizeof(jchar) * glyphsCount);
@@ -673,13 +674,25 @@
}
}
- static int breakText(JNIEnv* env, const SkPaint& paint, const jchar text[],
+ static int breakText(JNIEnv* env, SkPaint& paint, const jchar text[],
int count, float maxWidth, jfloatArray jmeasured,
SkPaint::TextBufferDirection tbd) {
- SkASSERT(paint.getTextEncoding() == SkPaint::kUTF16_TextEncoding);
+ sp<TextLayoutCacheValue> value;
+#if USE_TEXT_LAYOUT_CACHE
+ value = TextLayoutCache::getInstance().getValue(&paint, text, 0, count,
+ count, paint.getFlags());
+ if (value == NULL) {
+ ALOGE("Cannot get TextLayoutCache value for text = '%s'",
+ String8(text, count).string());
+ }
+#else
+ value = new TextLayoutCacheValue(count);
+ TextLayoutEngine::getInstance().computeValues(value.get(), &paint,
+ reinterpret_cast<const UChar*>(text), 0, count, count, paint.getFlags());
+#endif
SkScalar measured;
- size_t bytes = paint.breakText(text, count << 1,
+ size_t bytes = paint.breakText(value->getGlyphs(), value->getGlyphsCount() << 1,
SkFloatToScalar(maxWidth), &measured, tbd);
SkASSERT((bytes & 1) == 0);
@@ -743,7 +756,20 @@
SkRect r;
SkIRect ir;
- paint.measureText(text, count << 1, &r);
+ sp<TextLayoutCacheValue> value;
+#if USE_TEXT_LAYOUT_CACHE
+ value = TextLayoutCache::getInstance().getValue(&paint, text, 0, count,
+ count, paint.getFlags());
+ if (value == NULL) {
+ ALOGE("Cannot get TextLayoutCache value for text = '%s'",
+ String8(text, count).string());
+ }
+#else
+ value = new TextLayoutCacheValue(count);
+ TextLayoutEngine::getInstance().computeValues(value.get(), &paint,
+ reinterpret_cast<const UChar*>(text), 0, count, count, paint.getFlags());
+#endif
+ paint.measureText(value->getGlyphs(), value->getGlyphsCount() << 1, &r);
r.roundOut(&ir);
GraphicsJNI::irect_to_jrect(ir, env, bounds);
}
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index d26f563..e0d299bb 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -93,7 +93,7 @@
/*
* Caching
*/
-sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint,
+sp<TextLayoutCacheValue> TextLayoutCache::getValue(const SkPaint* paint,
const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
AutoMutex _l(mLock);
nsecs_t startTime = 0;
@@ -360,7 +360,7 @@
// we don't bother at the moment
}
-void TextLayoutEngine::computeValues(TextLayoutCacheValue* value, SkPaint* paint, const UChar* chars,
+void TextLayoutEngine::computeValues(TextLayoutCacheValue* value, const SkPaint* paint, const UChar* chars,
size_t start, size_t count, size_t contextCount, int dirFlags) {
computeValues(paint, chars, start, count, contextCount, dirFlags,
@@ -371,7 +371,7 @@
#endif
}
-void TextLayoutEngine::computeValues(SkPaint* paint, const UChar* chars,
+void TextLayoutEngine::computeValues(const SkPaint* paint, const UChar* chars,
size_t start, size_t count, size_t contextCount, int dirFlags,
Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
Vector<jchar>* const outGlyphs) {
@@ -513,7 +513,7 @@
}
}
-void TextLayoutEngine::computeRunValues(SkPaint* paint, const UChar* chars,
+void TextLayoutEngine::computeRunValues(const SkPaint* paint, const UChar* chars,
size_t count, bool isRTL,
Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
Vector<jchar>* const outGlyphs) {
@@ -719,7 +719,7 @@
}
-size_t TextLayoutEngine::shapeFontRun(SkPaint* paint, bool isRTL) {
+size_t TextLayoutEngine::shapeFontRun(const SkPaint* paint, bool isRTL) {
// Reset kerning
mShaperItem.kerning_applied = false;
@@ -811,7 +811,7 @@
case HB_Script_Hebrew:
case HB_Script_Bengali:
case HB_Script_Thai:{
- const uint16_t* text16 = (const uint16_t*)mShaperItem.string;
+ const uint16_t* text16 = (const uint16_t*)(mShaperItem.string + mShaperItem.item.pos);
SkUnichar firstUnichar = SkUTF16_NextUnichar(&text16);
baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
break;
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index 510aa18..956e8ca 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -179,7 +179,7 @@
*/
void operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc);
- sp<TextLayoutCacheValue> getValue(SkPaint* paint, const jchar* text, jint start, jint count,
+ sp<TextLayoutCacheValue> getValue(const SkPaint* paint, const jchar* text, jint start, jint count,
jint contextCount, jint dirFlags);
/**
@@ -224,7 +224,7 @@
TextLayoutEngine();
virtual ~TextLayoutEngine();
- void computeValues(TextLayoutCacheValue* value, SkPaint* paint, const UChar* chars,
+ void computeValues(TextLayoutCacheValue* value, const SkPaint* paint, const UChar* chars,
size_t start, size_t count, size_t contextCount, int dirFlags);
private:
@@ -273,14 +273,14 @@
*/
UnicodeString mBuffer;
- size_t shapeFontRun(SkPaint* paint, bool isRTL);
+ size_t shapeFontRun(const SkPaint* paint, bool isRTL);
- void computeValues(SkPaint* paint, const UChar* chars,
+ void computeValues(const SkPaint* paint, const UChar* chars,
size_t start, size_t count, size_t contextCount, int dirFlags,
Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
Vector<jchar>* const outGlyphs);
- void computeRunValues(SkPaint* paint, const UChar* chars,
+ void computeRunValues(const SkPaint* paint, const UChar* chars,
size_t count, bool isRTL,
Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
Vector<jchar>* const outGlyphs);
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 064dcac..5811ddd 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -483,6 +483,20 @@
}
// ----------------------------------------------------------------------------
+// Draw filters
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_setupPaintFilter(JNIEnv* env, jobject clazz,
+ OpenGLRenderer* renderer, jint clearBits, jint setBits) {
+ renderer->setupPaintFilter(clearBits, setBits);
+}
+
+static void android_view_GLES20Canvas_resetPaintFilter(JNIEnv* env, jobject clazz,
+ OpenGLRenderer* renderer) {
+ renderer->resetPaintFilter();
+}
+
+// ----------------------------------------------------------------------------
// Text
// ----------------------------------------------------------------------------
@@ -870,6 +884,9 @@
{ "nSetupColorFilter", "(II)V", (void*) android_view_GLES20Canvas_setupColorFilter },
{ "nSetupShadow", "(IFFFI)V", (void*) android_view_GLES20Canvas_setupShadow },
+ { "nSetupPaintFilter", "(III)V", (void*) android_view_GLES20Canvas_setupPaintFilter },
+ { "nResetPaintFilter", "(I)V", (void*) android_view_GLES20Canvas_resetPaintFilter },
+
{ "nDrawText", "(I[CIIFFII)V", (void*) android_view_GLES20Canvas_drawTextArray },
{ "nDrawText", "(ILjava/lang/String;IIFFII)V",
(void*) android_view_GLES20Canvas_drawText },
diff --git a/core/jni/android_view_HardwareRenderer.cpp b/core/jni/android_view_HardwareRenderer.cpp
index 09809ec..cdcde51 100644
--- a/core/jni/android_view_HardwareRenderer.cpp
+++ b/core/jni/android_view_HardwareRenderer.cpp
@@ -22,6 +22,8 @@
#include <EGL/egl_cache.h>
+EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface);
+
namespace android {
// ----------------------------------------------------------------------------
@@ -36,6 +38,12 @@
env->ReleaseStringUTFChars(diskCachePath, cacheArray);
}
+static void android_view_HardwareRenderer_beginFrame(JNIEnv* env, jobject clazz) {
+ EGLDisplay dpy = eglGetCurrentDisplay();
+ EGLSurface surf = eglGetCurrentSurface(EGL_DRAW);
+ eglBeginFrame(dpy, surf);
+}
+
// ----------------------------------------------------------------------------
// JNI Glue
// ----------------------------------------------------------------------------
@@ -45,6 +53,8 @@
static JNINativeMethod gMethods[] = {
{ "nSetupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_HardwareRenderer_setupShadersDiskCache },
+ { "nBeginFrame", "()V",
+ (void*) android_view_HardwareRenderer_beginFrame },
};
int register_android_view_HardwareRenderer(JNIEnv* env) {
diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml
index 95cc562..470416b 100644
--- a/core/res/res/anim/screen_rotate_180_enter.xml
+++ b/core/res/res/anim/screen_rotate_180_enter.xml
@@ -19,16 +19,10 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
- <scale android:fromXScale="1.0" android:toXScale="1.0"
- android:fromYScale=".9" android:toYScale="1.0"
- android:pivotX="50%p" android:pivotY="50%p"
- android:fillEnabled="true" android:fillBefore="true"
+ <rotate android:fromDegrees="180" android:toDegrees="0"
+ android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
- android:startOffset="160"
- android:duration="300" />
- <alpha android:fromAlpha="0" android:toAlpha="1.0"
- android:fillEnabled="true" android:fillBefore="true"
- android:interpolator="@interpolator/decelerate_quint"
- android:startOffset="160"
- android:duration="300"/>
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml
index d3dd4c0..58a1868 100644
--- a/core/res/res/anim/screen_rotate_180_exit.xml
+++ b/core/res/res/anim/screen_rotate_180_exit.xml
@@ -19,12 +19,10 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
- <scale android:fromXScale="1.0" android:toXScale="1.0"
- android:fromYScale="1.0" android:toYScale="0.0"
- android:pivotX="50%p" android:pivotY="50%p"
- android:interpolator="@interpolator/accelerate_cubic"
- android:duration="160" />
- <alpha android:fromAlpha="1.0" android:toAlpha="0"
- android:interpolator="@interpolator/decelerate_cubic"
- android:duration="160"/>
+ <rotate android:fromDegrees="0" android:toDegrees="-180"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/screen_rotate_finish_enter.xml b/core/res/res/anim/screen_rotate_finish_enter.xml
new file mode 100644
index 0000000..849aa66
--- /dev/null
+++ b/core/res/res/anim/screen_rotate_finish_enter.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <scale android:fromXScale="1.0" android:toXScale="1.25"
+ android:fromYScale="1.0" android:toYScale="1.25"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="100%p" android:toXScale="100%"
+ android:fromYScale="100%p" android:toYScale="100%"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+</set>
diff --git a/core/res/res/anim/screen_rotate_finish_exit.xml b/core/res/res/anim/screen_rotate_finish_exit.xml
new file mode 100644
index 0000000..7f70dbc
--- /dev/null
+++ b/core/res/res/anim/screen_rotate_finish_exit.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <scale android:fromXScale="1.0" android:toXScale="1.25"
+ android:fromYScale="1.0" android:toYScale="1.25"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <!--
+ <scale android:fromXScale="100%" android:toXScale="100%p"
+ android:fromYScale="100%" android:toYScale="100%p"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+</set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml
index 61aa72a..d2aebc9 100644
--- a/core/res/res/anim/screen_rotate_minus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml
@@ -19,8 +19,44 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+ <!--
+ <scale android:fromXScale="1.0" android:toXScale="0.565"
+ android:fromYScale="1.0" android:toYScale="0.565"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="1.0" android:toXScale="1.777777777"
+ android:fromYScale="1.0" android:toYScale="1.777777777"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:startOffset="@android:integer/config_longAnimTime"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="100%p" android:toXScale="100%"
+ android:fromYScale="100%p" android:toYScale="100%"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:startOffset="@android:integer/config_longAnimTime"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
+ <!--
+ <scale android:fromXScale="100%p" android:toXScale="100%"
+ android:fromYScale="100%p" android:toYScale="100%"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
<rotate android:fromDegrees="-90" android:toDegrees="0"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml
index 65294f6..c7c38cd 100644
--- a/core/res/res/anim/screen_rotate_minus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml
@@ -19,16 +19,51 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+ <!--
+ <scale android:fromXScale="1.0" android:toXScale="0.565"
+ android:fromYScale="1.0" android:toYScale="0.565"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="1.0" android:toXScale="1.777777777"
+ android:fromYScale="1.0" android:toYScale="1.777777777"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:startOffset="@android:integer/config_longAnimTime"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="100%" android:toXScale="100%p"
+ android:fromYScale="100%" android:toYScale="100%p"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:startOffset="@android:integer/config_longAnimTime"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:startOffset="@android:integer/config_longAnimTime"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
+ <!--
<scale android:fromXScale="100%" android:toXScale="100%p"
android:fromYScale="100%" android:toYScale="100%p"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
android:duration="@android:integer/config_mediumAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
<rotate android:fromDegrees="0" android:toDegrees="90"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
- android:duration="@android:integer/config_mediumAnimTime" />
- <alpha android:fromAlpha="1.0" android:toAlpha="0"
- android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml
index 53b0ccd..63d7043 100644
--- a/core/res/res/anim/screen_rotate_plus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml
@@ -19,8 +19,44 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+ <!--
+ <scale android:fromXScale="1.0" android:toXScale="0.565"
+ android:fromYScale="1.0" android:toYScale="0.565"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="1.0" android:toXScale="1.777777777"
+ android:fromYScale="1.0" android:toYScale="1.777777777"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:startOffset="75"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="100%p" android:toXScale="100%"
+ android:fromYScale="100%p" android:toYScale="100%"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:startOffset="75"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
+ <!--
+ <scale android:fromXScale="100%p" android:toXScale="100%"
+ android:fromYScale="100%p" android:toYScale="100%"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
<rotate android:fromDegrees="90" android:toDegrees="0"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml
index 63c0b09..ea48c81 100644
--- a/core/res/res/anim/screen_rotate_plus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml
@@ -19,16 +19,51 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+ <!--
+ <scale android:fromXScale="1.0" android:toXScale="0.565"
+ android:fromYScale="1.0" android:toYScale="0.565"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="1.0" android:toXScale="1.777777777"
+ android:fromYScale="1.0" android:toYScale="1.777777777"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:startOffset="75"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+ <scale android:fromXScale="100%" android:toXScale="100%p"
+ android:fromYScale="100%" android:toYScale="100%p"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:startOffset="75"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:startOffset="75"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
+ <!--
<scale android:fromXScale="100%" android:toXScale="100%p"
android:fromYScale="100%" android:toYScale="100%p"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
android:duration="@android:integer/config_mediumAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="false" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ -->
<rotate android:fromDegrees="0" android:toDegrees="-90"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
- android:duration="@android:integer/config_mediumAnimTime" />
- <alpha android:fromAlpha="1.0" android:toAlpha="0"
- android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_start_enter.xml b/core/res/res/anim/screen_rotate_start_enter.xml
new file mode 100644
index 0000000..e3f48e4d
--- /dev/null
+++ b/core/res/res/anim/screen_rotate_start_enter.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <scale android:fromXScale="1.0" android:toXScale="0.8"
+ android:fromYScale="1.0" android:toYScale="0.8"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
diff --git a/core/res/res/anim/screen_rotate_start_exit.xml b/core/res/res/anim/screen_rotate_start_exit.xml
new file mode 100644
index 0000000..e3f48e4d
--- /dev/null
+++ b/core/res/res/anim/screen_rotate_start_exit.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <scale android:fromXScale="1.0" android:toXScale="0.8"
+ android:fromYScale="1.0" android:toYScale="0.8"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime"/>
+</set>
diff --git a/core/res/res/menu/webview_copy.xml b/core/res/res/menu/webview_copy.xml
index 31bcec5..6a1aa51 100644
--- a/core/res/res/menu/webview_copy.xml
+++ b/core/res/res/menu/webview_copy.xml
@@ -19,11 +19,21 @@
android:title="@string/selectAll"
android:showAsAction="ifRoom|withText"
/>
+ <item android:id="@+id/cut"
+ android:icon="?android:attr/actionModeCutDrawable"
+ android:title="@string/cut"
+ android:showAsAction="ifRoom|withText"
+ />
<item android:id="@+id/copy"
android:icon="?android:attr/actionModeCopyDrawable"
android:title="@string/copy"
android:showAsAction="ifRoom|withText"
/>
+ <item android:id="@+id/paste"
+ android:icon="?android:attr/actionModePasteDrawable"
+ android:title="@string/paste"
+ android:showAsAction="ifRoom|withText"
+ />
<item android:id="@+id/share"
android:icon="?android:attr/actionModeShareDrawable"
android:title="@string/share"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 545a555..b514bf5 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1830,12 +1830,12 @@
<!-- Defines whether the vertical scrollbar track should always be drawn. -->
<attr name="scrollbarAlwaysDrawVerticalTrack" format="boolean" />
- <!-- {@deprecated This attribute is deprecated and will be ignored as of
- API level 14 (<code>android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH</code>).
+ <!-- This attribute is deprecated and will be ignored as of
+ API level 14 ({@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}).
Using fading edges may introduce noticeable performance
degradations and should be used only when required by the application's
visual design. To request fading edges with API level 14 and above,
- use the <code>requiresFadingEdge</code> attribute instead.} -->
+ use the <code>android:requiresFadingEdge</code> attribute instead. -->
<attr name="fadingEdge">
<!-- No edge is faded. -->
<flag name="none" value="0x00000000" />
diff --git a/core/tests/coretests/src/android/util/InternalSelectionView.java b/core/tests/coretests/src/android/util/InternalSelectionView.java
index babf38d..a0fb0f1 100644
--- a/core/tests/coretests/src/android/util/InternalSelectionView.java
+++ b/core/tests/coretests/src/android/util/InternalSelectionView.java
@@ -36,6 +36,11 @@
* entire width of the view. The height of the view is divided evenly among
* the rows.
*
+ * Note: If the height of the view does not divide exactly to the number of rows,
+ * the last row's height is inflated with the remainder. For example, if the
+ * view height is 22 and there are two rows, the height of the first row is
+ * 10 and the second 22.
+ *
* Notice what this view does to be a good citizen w.r.t its internal selection:
* 1) calls {@link View#requestRectangleOnScreen} each time the selection changes due to
* internal navigation.
@@ -138,9 +143,6 @@
@Override
protected void onDraw(Canvas canvas) {
-
- int rowHeight = getRowHeight();
-
int rectTop = mPaddingTop;
int rectLeft = mPaddingLeft;
int rectRight = getWidth() - mPaddingRight;
@@ -149,6 +151,8 @@
mPainter.setColor(Color.BLACK);
mPainter.setAlpha(0x20);
+ int rowHeight = getRowHeight(i);
+
// draw background rect
mTempRect.set(rectLeft, rectTop, rectRight, rectTop + rowHeight);
canvas.drawRect(mTempRect, mPainter);
@@ -178,12 +182,19 @@
}
}
- private int getRowHeight() {
- return (getHeight() - mPaddingTop - mPaddingBottom) / mNumRows;
+ private int getRowHeight(int row) {
+ final int availableHeight = getHeight() - mPaddingTop - mPaddingBottom;
+ final int desiredRowHeight = availableHeight / mNumRows;
+ if (row < mNumRows - 1) {
+ return desiredRowHeight;
+ } else {
+ final int residualHeight = availableHeight % mNumRows;
+ return desiredRowHeight + residualHeight;
+ }
}
public void getRectForRow(Rect rect, int row) {
- final int rowHeight = getRowHeight();
+ final int rowHeight = getRowHeight(row);
final int top = mPaddingTop + row * rowHeight;
rect.set(mPaddingLeft,
top,
@@ -197,10 +208,7 @@
requestRectangleOnScreen(mTempRect);
}
-
- /* (non-Javadoc)
- * @see android.view.KeyEvent.Callback#onKeyDown(int, android.view.KeyEvent)
- */
+ @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch(event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_UP:
diff --git a/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java b/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java
index 6518341..53b866c 100644
--- a/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java
+++ b/core/tests/coretests/src/android/widget/focus/ListOfInternalSelectionViews.java
@@ -17,6 +17,7 @@
package android.widget.focus;
import android.app.Activity;
+import android.graphics.Point;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
@@ -110,7 +111,10 @@
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
- mScreenHeight = getWindowManager().getDefaultDisplay().getHeight();
+
+ Point size = new Point();
+ getWindowManager().getDefaultDisplay().getSize(size);
+ mScreenHeight = size.y;
Bundle extras = getIntent().getExtras();
if (extras != null) {
diff --git a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
index 909a8c9..a78b0c9 100644
--- a/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
+++ b/core/tests/coretests/src/android/widget/focus/RequestFocusTest.java
@@ -90,7 +90,7 @@
fail("requestFocus from wrong thread should raise exception.");
} catch (AndroidRuntimeException e) {
// Expected. The actual exception is not public, so we can't catch it.
- assertEquals("android.view.ViewAncestor$CalledFromWrongThreadException",
+ assertEquals("android.view.ViewRootImpl$CalledFromWrongThreadException",
e.getClass().getName());
}
}
diff --git a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
index eb9192a..795e09c 100644
--- a/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
+++ b/core/tests/coretests/src/android/widget/focus/ScrollingThroughListOfFocusablesTest.java
@@ -20,10 +20,9 @@
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.MediumTest;
+import android.util.InternalSelectionView;
import android.view.KeyEvent;
import android.widget.ListView;
-import android.widget.focus.ListOfInternalSelectionViews;
-import android.util.InternalSelectionView;
/**
@@ -51,6 +50,10 @@
mNumRowsPerItem, // 5 internally selectable rows per item
mScreenHeightFactor)); // each item is 5 / 4 screen height tall
mListView = mActivity.getListView();
+ // Make sure we have some fading edge regardless of ListView style.
+ mListView.setVerticalFadingEdgeEnabled(true);
+ mListView.setFadingEdgeLength(10);
+ ensureNotInTouchMode();
}
@Override
@@ -67,12 +70,12 @@
assertEquals(mNumRowsPerItem, mActivity.getNumRowsPerItem());
}
- // TODO: needs to be adjusted to pass on non-HVGA displays
- // @MediumTest
+ @MediumTest
public void testScrollingDownInFirstItem() throws Exception {
for (int i = 0; i < mNumRowsPerItem; i++) {
assertEquals(0, mListView.getSelectedItemPosition());
+
InternalSelectionView view = mActivity.getSelectedView();
assertInternallySelectedRowOnScreen(view, i);
@@ -90,13 +93,12 @@
mListView.getSelectedView();
// 1 pixel tolerance in case height / 4 is not an even number
- final int fadingEdge = mListView.getBottom() - mListView.getVerticalFadingEdgeLength();
+ final int bottomFadingEdgeTop =
+ mListView.getBottom() - mListView.getVerticalFadingEdgeLength();
assertTrue("bottom of view should be just above fading edge",
- view.getBottom() >= fadingEdge - 1 &&
- view.getBottom() <= fadingEdge);
+ view.getBottom() == bottomFadingEdgeTop);
}
-
// make sure fading edge is the expected view
{
assertEquals("should be a second view visible due to the fading edge",
@@ -109,7 +111,6 @@
}
}
-
@MediumTest
public void testScrollingToSecondItem() throws Exception {
@@ -223,4 +224,12 @@
assertTrue("bottom of row " + row + " should be on sreen",
mTempRect.bottom < mActivity.getScreenHeight());
}
+
+ private void ensureNotInTouchMode() {
+ // If in touch mode inject a DPAD down event to exit that mode.
+ if (mListView.isInTouchMode()) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+ }
+ }
}
diff --git a/docs/html/design/get-started/ui-overview.html b/docs/html/design/get-started/ui-overview.html
index b08c743..bd5ff9c 100644
--- a/docs/html/design/get-started/ui-overview.html
+++ b/docs/html/design/get-started/ui-overview.html
@@ -153,12 +153,12 @@
</div>
</div>
-<h2>UI Bars</h2>
+<h2>System Bars</h2>
-<p>The UI bars are screen areas dedicated to the display of notifications, communication of device
-status, and device navigation. Typically the UI bars are displayed concurrently with your app. Apps
-that display immersive content, such as movies or images, can temporarily hide the UI bars to allow
-the user to enjoy full screen content without distraction.</p>
+<p>The system bars are screen areas dedicated to the display of notifications, communication of device
+status, and device navigation. Typically the system bars are displayed concurrently with your app.
+Apps that display immersive content, such as movies or images, can temporarily hide the system bars
+to allow the user to enjoy full screen content without distraction.</p>
<img src="../static/content/ui_overview_system_ui.png">
@@ -177,8 +177,9 @@
Recents, and also displays a menu for apps written for Android 2.3 or earlier.</p>
</li>
<li>
-<h4>System Bar</h4>
-<p>Combines the status and navigation bars for display on tablet form factors.</p>
+<h4>Combined Bar</h4>
+<p>On tablet form factors the status and navigation bars are combined into a single bar at the
+ bottom of the screen.</p>
</li>
</ol>
diff --git a/docs/html/design/patterns/app-structure.html b/docs/html/design/patterns/app-structure.html
index b87f402..fb9205b 100644
--- a/docs/html/design/patterns/app-structure.html
+++ b/docs/html/design/patterns/app-structure.html
@@ -159,9 +159,9 @@
<img src="../static/content/app_structure_market.png">
<div class="figure-caption">
- Market's start screen primarily allows navigation into the stores for Apps, Music, Books, and
- Games. It is also enriched with tailored recommendations and promotions that surface content
- of interest to the user. Search is readily available from the action bar.
+ Market's start screen primarily allows navigation into the stores for Apps, Music, Books,
+ Movies and Games. It is also enriched with tailored recommendations and promotions that
+ surface content of interest to the user. Search is readily available from the action bar.
</div>
</div>
diff --git a/docs/html/design/patterns/navigation.html b/docs/html/design/patterns/navigation.html
index aabfc39..cad3682 100644
--- a/docs/html/design/patterns/navigation.html
+++ b/docs/html/design/patterns/navigation.html
@@ -131,7 +131,7 @@
<p>The Back key also supports a few behaviors not directly tied to screen-to-screen navigation:</p>
<ul>
<li>Back dismisses floating windows (dialogs, popups)</li>
-<li>Back dismisses contextual action bars, and remove highlight from selected items</li>
+<li>Back dismisses contextual action bars, and removes the highlight from the selected items</li>
<li>Back hides the onscreen keyboard (IME)</li>
</ul>
<h2>Navigation Within Your App</h2>
@@ -189,18 +189,19 @@
<h4>App-to-app navigation</h4>
<p>When navigating deep into your app's hierarchy directly from another app via an intent, Back will
return to the referring app.</p>
-<p>The Up button is handled is follows:
+<p>The Up button is handled as follows:
- If the destination screen is typically reached from one particular screen within your app, Up
should navigate to that screen.
- Otherwise, Up should navigate to the topmost ("Home") screen of your app.</p>
-<p>For example, after choosing to share a book being view in Market, the user navigates directly to the
-Gmail's compose screen. From there, Up returns to the Inbox (which happens to be both the typical
-referrer to compose, as well as the topmost screen of the app), while Back returns to Market.</p>
+<p>For example, after choosing to share a book being viewed in Market, the user navigates directly to
+Gmail's compose screen. From there, Up returns to the Inbox (which happens to be both the
+typical referrer to compose, as well as the topmost screen of the app), while Back returns to
+Market.</p>
<img src="../static/content/navigation_from_outside_up.png">
<h4>System-to-app navigation</h4>
-<p>If the your app was reached via the system mechanisms of notifications or home screen widgets, Up
+<p>If your app was reached via the system mechanisms of notifications or home screen widgets, Up
behaves as described for app-to-app navigation, above.</p>
<p>For the Back key, you should make navigation more predictably by inserting into the task's back
stack the complete upward navigation path to the app's topmost screen. This way, a user who has
diff --git a/docs/html/design/patterns/notifications.html b/docs/html/design/patterns/notifications.html
index acec306..c5045aed 100644
--- a/docs/html/design/patterns/notifications.html
+++ b/docs/html/design/patterns/notifications.html
@@ -200,7 +200,7 @@
the user is taken to a hierarchy level below your app's top-level, insert navigation into your app's
back stack to allow them to navigate to your app's top level using the system back key. For more
information, see the chapter on <em>System-to-app navigation</em> in the
-<a href="../patterns/notifications.html">Navigation</a> design pattern.</p>
+<a href="../patterns/navigation.html">Navigation</a> design pattern.</p>
<h4>Timestamps for time sensitive events</h4>
<p>By default, standard Android notifications include a timestamp in the upper right corner. Consider
whether the timestamp is valuable in the context of your notification. If the timestamp is not
diff --git a/docs/html/design/static/download/action_bar_icons-v4.0.zip b/docs/html/design/static/download/action_bar_icons-v4.0.zip
new file mode 100644
index 0000000..e255931
--- /dev/null
+++ b/docs/html/design/static/download/action_bar_icons-v4.0.zip
Binary files differ
diff --git a/docs/html/guide/developing/device.jd b/docs/html/guide/developing/device.jd
index c4d08ed..e46d07c 100644
--- a/docs/html/guide/developing/device.jd
+++ b/docs/html/guide/developing/device.jd
@@ -154,6 +154,14 @@
<td><code>0489</code></td>
</tr>
<tr>
+ <td>Fujitsu</td>
+ <td><code>04C5</code></td>
+ </tr>
+ <tr>
+ <td>Fujitsu Toshiba</td>
+ <td><code>04C5</code></td>
+ </tr>
+ <tr>
<td>Garmin-Asus</td>
<td><code>091E</code></td>
</tr>
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index ee4c48e..4e5badd 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -87,10 +87,24 @@
<span class="en">Content Providers</span>
</a></div>
<ul>
- <li><a href="<?cs var:toroot ?>guide/topics/providers/calendar-provider.html">
- <span class="en">Calendar Provider</span></a>
- <span class="new">new!</span>
- </li>
+ <li>
+ <a href="<?cs var:toroot ?>guide/topics/providers/content-provider-basics.html">
+ <span class="en">Content Provider Basics</span>
+ </a>
+ <span class="new">new!</span>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>guide/topics/providers/content-provider-creating.html">
+ <span class="en">Creating a Content Provider</span>
+ </a>
+ <span class="new">new!</span>
+ </li>
+ <li>
+ <a href="<?cs var:toroot ?>guide/topics/providers/calendar-provider.html">
+ <span class="en">Calendar Provider</span>
+ </a>
+ <span class="new">new!</span>
+ </li>
</ul>
</li>
<li><a href="<?cs var:toroot ?>guide/topics/intents/intents-filters.html">
diff --git a/docs/html/guide/practices/ui_guidelines/icon_design.jd b/docs/html/guide/practices/ui_guidelines/icon_design.jd
index 96aecf5..1c66185 100644
--- a/docs/html/guide/practices/ui_guidelines/icon_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/icon_design.jd
@@ -42,8 +42,6 @@
Templates Pack, v2.3 »</a></li>
<li><a href="{@docRoot}shareables/icon_templates-v2.0.zip">Android Icon
Templates Pack, v2.0 »</a></li>
-<li><a href="{@docRoot}shareables/icon_templates-v1.0.zip">Android Icon
-Templates Pack, v1.0 »</a></li>
</ol>
<h2>See also</h2>
diff --git a/docs/html/guide/publishing/preparing.jd b/docs/html/guide/publishing/preparing.jd
index 4d3bffa..83aa5ee 100644
--- a/docs/html/guide/publishing/preparing.jd
+++ b/docs/html/guide/publishing/preparing.jd
@@ -117,6 +117,9 @@
certificate requirements, see <a href="{@docRoot}guide/publishing/app-signing.html#cert">Obtain a
suitable private key</a>.</p>
+<p class="caution"><strong>Important:</strong> Your application must be signed with a cryptographic
+key whose validity period ends after 22 October 2033.</p>
+
<p>You may also have to obtain other release keys if your application accesses a service or uses a
third-party library that requires you to use a key that is based on your private key. For example,
if your application uses the <a
diff --git a/docs/html/guide/publishing/publishing.jd b/docs/html/guide/publishing/publishing.jd
index fa677e6..49b34d8 100644
--- a/docs/html/guide/publishing/publishing.jd
+++ b/docs/html/guide/publishing/publishing.jd
@@ -7,9 +7,9 @@
<h2>Quickview</h2>
<ul>
-<li>You can publish your application using a hosted service such as Android Market or through a web server.</li>
-<li>Before you publish, make sure you have prepared your application properly.</li>
-<li>Android Market makes it easy for users of Android-powered devices to see and download your application.</li>
+<li>Learn how to publish and update apps on Android Market.</li>
+<li>Find out how to create links to apps that are published on Android Market.</li>
+<li>Learn about Android Market features.</li>
</ul>
@@ -17,97 +17,201 @@
<ol>
<li><a href="#overview">About Android Market</a>
+<li><A href="#marketpublish">Publishing Apps on Android Market</a></li>
<li><a href="#marketupgrade">Publishing Updates on Android Market</a></li>
<li><a href="#marketLicensing">Using Android Market Licensing Service</a></li>
+<li><a href="#marketinappbilling">Using Android Market In-app Billing</a></li>
<li><a href="#marketintent">Linking to Your Apps on Android Market</a>
<ol>
<li><a href="#OpeningDetails">Opening an app's details page</a></li>
<li><a href="#PerformingSearch">Performing a search</a></li>
<li><a href="#BuildaButton">Build an Android Market button</a></li>
<li><a href="#UriSummary">Summary of URI formats</a></li>
- </ol>
+ </ol>
</li>
</ol>
<h2>See also</h2>
<ol>
-<li><a href="{@docRoot}guide/publishing/licensing.html">Application Licensing</a></li>
-<li><a href="{@docRoot}guide/publishing/preparing.html">Preparing to Publish</a></li>
+<li><a href="{@docRoot}guide/publishing/publishing_overview.html">Publishing Overview</a></li>
+<li><a href="{@docRoot}guide/publishing/preparing.html">Preparing for Release</a></li>
</ol>
<div id="qv-extra">
<img id="rule" src="{@docRoot}assets/images/grad-rule-qv.png">
<div id="qv-sub-rule">
<img src="{@docRoot}assets/images/icon_market.jpg" style="float:left;margin:0;padding:0 5px;">
- <h2 style="color:#669999;">Interested in publishing your app on Android Market?</h2>
- <p><a href="http://market.android.com/publish">Go to Android Market</a> to
-create a developer account and upload your application. For more information about the
-required assets, listing details, and options, see <a
-href="http://market.android.com/support/bin/answer.py?answer=113469">Uploading
-applications</a>.</p>
+ <h2 style="color:#669999;">Already know about Android Market and want to get started?</h2>
+ <p>Go to <a href="http://market.android.com/publish">Android Market</a>, create a developer
+account, and upload your application. For more information about required assets, listing details,
+and publishing options, see <a
+href="http://market.android.com/support/bin/answer.py?answer=113469">Upload
+Applications</a>.</p>
</div>
</div>
</div>
</div>
-<p>If you've followed the steps outlined in <a
-href="{@docRoot}guide/publishing/preparing.html">Preparing to Publish</a>, the result of the process
-is a compiled {@code .apk} file that is signed with your private release key. Your application is
-now ready to be published publicly so users can install it.</p>
+<p>One of the most effective ways to get your application into users' hands is to
+publish it on an application marketplace like Android Market. Publishing on Android Market is a
+straightforward process that you can do in just a few simple steps—register, configure,
+upload, and publish. Registration takes only a few minutes and needs to be done only once.
+The configuration and publishing steps can all be done through the Android Market Developer Console
+after you register as an Android Market developer.</p>
-<p>You can publish your application and allow users to install it any way you choose, including
-from your own web server. This document provides information about publishing your Android
-application with Android Market.</p>
+<p>To start publishing on Android Market, first read this topic and then go to the <a
+href="https://market.android.com/publish/signup">Android Market publisher site</a> and register as
+an Android Market developer.</p>
<h2 id="overview">About Android Market</h2>
-<p>Android Market is a service that makes it easy for users to find and download Android
-applications to their Android-powered devices, either from the Android Market application on their
-device or from the Android Market web site (<a
-href="http://market.android.com">market.android.com</a>). As a developer, you can use Android Market
-to distribute your applications to users on all types of Android-powered devices, all around the
-world.</p>
+<p>Android Market is a robust publishing platform that helps you publicize, sell, and distribute
+your Android applications to users around the world. When you release your applications through
+Android Market you have access to a suite of developer tools that let you analyze your sales,
+identify market trends, and control who your applications are being distributed to. You also have
+access to several revenue-enhancing features, such as <a
+href="{@docRoot}guide/market/billing/index.html">in-app billing</a> and
+<a href="{@docRoot}guide/publishing/licensing.html">application licensing</a>.</p>
-<p>To publish your application on Android Market, you first need to register
-with the service using a Google account and agree to the terms of service.
-Once you are registered, you can upload your application to the service whenever
-you want, update it as many times as you want, and then publish it when you are ready.
-Once published, users can see your application, download it, and rate it. </p>
+<p>Before you can publish applications on Android Market, you need to <a
+href="http://market.android.com/publish">register</a> as an Android Market developer. During the
+registration process you will need to create a developer profile, pay a registration fee, and agree
+to the <a href="http://www.android.com/us/developer-distribution-agreement.html">Android Market
+Developer Distribution Agreement</a>. After you register you can access the Android Market Developer
+Console, where you can upload applications, configure publishing options, and monitor publishing
+data. If you want to sell your applications or use the in-app billing feature, you will also need
+to set up a Google Checkout merchant account. For more information about the registration process,
+see <a href="https://support.google.com/androidmarket/developer/bin/answer.py?hl=en&answer=113468">
+Developer Registration</a>.</p>
-<p>To register as an Android Market developer and get started with publishing,
-visit the Android Market publisher site: </p>
+<h2 id="marketpublish">Publishing Apps on Android Market</h2>
-<p style="margin-left:3em;"><a
-href="http://market.android.com/publish">http://market.android.com/publish</a>
+<p>Publishing your application on Android Market is a simple process that involves three basic
+tasks (see figure 1):</p>
+
+<ul>
+ <li>Creating various graphical assets that
+accompany your app on Android Market.</li>
+ <li>Using the Android Market <a
+href="http://market.android.com/publish">Developer Console</a> to configure publishing options,
+specify listing details, and upload your app and graphical assets to Android Market.</li>
+ <li>Reviewing your publishing settings and changing the release
+status of your app from Unpublished to Published.</li>
+</ul>
+
+<img src="{@docRoot}images/publishing/publishing_android_market.png"
+ alt="Shows the three steps that are required to publish on Android Market"
+ height="168"
+ id="figure1" />
+<p class="img-caption">
+ <strong>Figure 1.</strong> To publish apps on Android Market you must first <a
+href="{@docRoot}guide/publishing/preparing.html">prepare your app for release</a> and then perform
+three simple tasks.
</p>
-<p>If you plan to publish your application on Android Market, you must make sure
-that it meets the requirements listed below, which are enforced by the Market
-server when you upload the application.</p>
+<p class="caution"><strong>Important:</strong> You must <a
+href="{@docRoot}guide/publishing/preparing.html">prepare your application for release</a> before you
+can publish it on Android Market. When you prepare your application for release you configure it for
+release and build it in release mode. Building in release mode signs your application's {@code .apk}
+file with your private release key. You cannot publish an application on Android Market unless it is
+signed with your own private release key.</p>
-<div class="special">
-<p>Requirements enforced by the Android Market server:</p>
-<ol>
-<li>Your application must be signed with a cryptographic private key whose
-validity period ends after <span style="color:red">22 October 2033</span>. </li>
-<li>Your application must define both an <code>android:versionCode</code> and an
-<code>android:versionName</code> attribute in the
+<h3>Preparing promotional materials</h3>
+
+<p>To fully leverage the marketing and publicity capabilities of Android Market, you need to create
+several graphical assets that accompany your app on Android Market, such as screenshots, videos,
+promotional graphics, and promotional text. At a minimum you must provide two screenshots of your
+application and a high resolution application icon. The screenshots are displayed on the details
+page for your application in Android Market, and the high resolution application icon is displayed
+in various locations throughout Android Market. The high resolution icon does not replace the
+launcher icon for your application, rather, it serves as a supplemental icon and should look
+the same as your launcher icon. Promotional video,
+graphics, and text are optional, although we strongly recommended that you prepare these for your
+app. For more information about the graphic assets that accompany your application, see <a
+href="http://support.google.com/androidmarket/developer/bin/answer.py?hl=en&answer=1078870">Graphic
+Assets for your Application</a>.</p>
+
+<h3>Configuring options and uploading assets</h3>
+
+<p>Android Market lets you target your application to a worldwide pool of users and devices. To
+reach these users you can use the Android Market Developer Console to configure various publishing
+options and listing details for your app. For example, you can choose the <a
+href="http://support.google.com/androidmarket/developer/bin/answer.py?hl=en&answer=138294&topic=
+2365624&ctx=topic">countries</a> you want to reach, the listing languages you want to use, and the
<a
-href="{@docRoot}guide/topics/manifest/manifest-element.html"><code><manifest></code></a>
-element of its manifest file. The server uses the <code>android:versionCode</code> as
-the basis for identifying the application internally and handling updates, and
-it displays the <code>android:versionName</code> to users as the application's
-version.</li>
-<li>Your application must define both an <code>android:icon</code> and an
-<code>android:label</code> attribute in the <a
-href="{@docRoot}guide/topics/manifest/application-element.html"><code><application></code></a>
-element of its manifest file.</li>
-</ol>
-</div>
+href="http://support.google.com/androidmarket/developer/bin/answer.py?hl=en&answer=138412&topic=
+15867&ctx=topic">price</a> you want to charge in each country. You can also configure listing
+details such as the application type, <a
+href="https://support.google.com/androidmarket/developer/bin/answer.py?hl=en&answer=113475&topic=
+2365760&ctx=topic">category</a>, and <a
+href="http://support.google.com/androidmarket/developer/bin/answer.py?hl=en&answer=188189&topic=
+2364761&ctx=topic">content rating</a>. In addition, if you want to sell items within your app using
+the in-app billing feature, you can use the Developer Console to <a
+href="http://grendel.sea.corp.google.com:48014/guide/market/billing/billing_admin.html#billing-list
+- setup">create a product list</a> and control which items are available for purchase in your
+app.</p>
+<p>When you are finished setting publishing options and listing details, you can upload your assets
+and your application to Android Market. You can also upload your application as a draft
+(unpublished) application, which lets you do final testing before you publish it for final
+release.</p>
+
+<p>To learn more about Android Market publishing settings, see the following resources:</p>
+
+<ul>
+ <li><a
+href="http://support.google.com/androidmarket/developer/bin/answer.py?hl=en&answer=113469&topic=
+236562&ctx=topic">Upload Applications</a>—provides a summary of the publishing settings
+you can configure for an app.</li>
+ <li><a
+href="http://support.google.com/androidmarket/developer/bin/topic.py?hl=en&topic=15867">Selling
+Your Apps</a>—provides guidance about pricing, supported currencies, tax rates, and many
+other topics related to selling apps.</li>
+ <li><a
+href="https://support.google.com/androidmarket/developer/bin/answer.py?hl=en&answer=1169947&topic=
+15867&ctx=topic">Selling Apps in Multiple Currencies</a>—provides a description of how
+pricing, payouts, and exchange rates work.</li>
+</ul>
+
+<h3>Publishing your application</h3>
+
+<p>When you are satisfied that your publishing settings are correctly configured and your uploaded
+application is ready to be released to the public, you can simply click <strong>Publish</strong> in
+the Developer Console to make your app available for download
+around the world. Keep in mind, it can take several hours for your app to appear on Android
+Market after you click <strong>Publish</strong> in the Developer Console.</p>
+
+<h3>Controlling Distribution to Devices</h3>
+
+<p>If your application targets different device configurations, you can control which Android-powered
+devices have access to your application on Android Market by
+using Android Market filters. Filtering compares device configurations that you declare in your
+app's manifest file to the configuration defined by a device. For example, if you declare the camera
+filter in your manifest, only those devices that have a camera will see your app on Android
+Market. Filters must be configured in your application's manifest file when you are <a
+href="{@docRoot}guide/publishing/preparing.html">preparing your app for release</a> (that is, before
+you upload your app to Android Market). For more information, see <a
+href="{@docRoot}guide/appendix/market-filters.html">Market Filters</a>.</p>
+
+<p>You can also use the multiple APK feature to distribute different {@code .apk} files under the same
+application listing and the same package name; however, you should use this option only as a last
+resort. Android applications usually run on most compatible devices with a single APK, by supplying
+alternative resources for different configurations (for example, different layouts for different screen
+sizes) and the Android system selects the appropriate resources for the device at runtime. In a
+few cases, however, a single APK is unable to support all device configurations, because alternative
+resources make the APK file too big (greater than 50MB) or other technical challenges prevent a
+single APK from working on all devices. Although we encourage you to develop and publish a single
+APK that supports as many device configurations as possible, doing so is sometimes
+not possible. To help you publish your application for as many devices as possible, Android Market
+allows you to publish multiple APKs under the same application listing. Android Market then supplies
+each APK to the appropriate devices based on configuration support you've declared in the manifest
+file of each APK. To use this feature, you need to build your separate {@code .apk} files when you are <a
+href="{@docRoot}guide/publishing/preparing.html">preparing your app for release</a> (that is, before
+you upload your app to Android Market). For more information, see <a
+href="{@docRoot}guide/market/publishing/multiple-apks.html">Multiple APK Support</a>.</p>
<h2 id="marketupgrade">Publishing Updates on Android Market</h2>
@@ -128,6 +232,9 @@
consider it a new application, publish it as such, and will not offer it to existing users as an
update.</p>
+<p>If you plan to publish your application on Android Market, you must make sure
+ that it meets the requirements listed below, which are enforced by the Market
+ server when you upload the application.</p>
<h2 id="marketLicensing">Using Android Market Licensing Service</h2>
@@ -136,7 +243,7 @@
Android Market Licensing, your applications can query Android Market at runtime
to obtain the licensing status for the current user, then allow or disallow
further use of the application as appropriate. Using the service, you can apply a flexible
-licensing policy on an application-by-application basis—each
+licensing policy on an application-by-application basis—each
application can enforce its licensing status in the way most appropriate
for it. </p>
@@ -149,7 +256,31 @@
use it in your application, read <a
href="{@docRoot}guide/publishing/licensing.html">Application Licensing</a>.</p>
+<h2 id="marketinappbilling">Using Android Market In-app Billing</h2>
+<p><a href="{@docRoot}guide/market/billing/billing_overview.html">Android Market In-app Billing</a>
+is an Android Market service that lets you sell digital content in your applications. You can use
+the service to sell a wide range of content, including downloadable content such as media files or
+photos, and virtual content such as game levels or potions.</p>
+
+<p>When you use Android Market's in-app billing service to sell an item, Android Market handles all
+billing details so your application never has to directly process any financial transactions.
+Android Market uses the same checkout service that is used for application purchases, so your users
+experience a consistent and familiar purchase flow (see figure 1). Also, the transaction fee for
+in-app purchases is the same as the transaction fee for application purchases (30%).</p>
+
+<p>Any application that you publish through Android Market can implement in-app billing. No special
+account or registration is required other than an Android Market publisher account and a Google
+Checkout Merchant account. Also, because the service uses no dedicated framework APIs, you can add
+in-app billing to any application that uses a minimum API level of 4 or higher.</p>
+
+<p>To help you integrate in-app billing into your application, the Android SDK provides a <a
+href="{@docRoot}guide/market/billing/billing_integrate.html#billing-download">sample application</a>
+that demonstrates a simple implementation of in-app billing. The sample application contains
+examples of billing-related classes you can use to implement in-app billing in your application. It
+also contains examples of the database, user interface, and business logic you might use to
+implement in-app billing. For more information about the in-app billing feature, see the
+<a href="{@docRoot}guide/market/billing/index.html">In-app Billing documentation</a>.</p>
<h2 id="marketintent">Linking to Your Apps on Android Market</h2>
@@ -337,7 +468,7 @@
Guidelines</a>.</p>
<style type="text/css">
-
+
form.button-form {
margin-top:2em;
}
@@ -539,7 +670,7 @@
<tr>
<td>Display the details screen for a specific application</td>
-<td><code>http://market.android.com/details?id=<package_name></code>
+<td><code>http://market.android.com/details?id=<package_name></code>
<td><code>market://details?id=<package_name></code></td>
</tr>
diff --git a/docs/html/guide/publishing/publishing_overview.jd b/docs/html/guide/publishing/publishing_overview.jd
index e30360b..79199c5 100755
--- a/docs/html/guide/publishing/publishing_overview.jd
+++ b/docs/html/guide/publishing/publishing_overview.jd
@@ -134,7 +134,7 @@
and features, coupled with numerous end-user community features, makes Android Market the premier
marketplace for selling and buying Android applications.</p>
-<p>Releasing your application on Android Market is a simple process that involves four basic
+<p>Releasing your application on Android Market is a simple process that involves three basic
steps:</p>
<div class="figure" style="width:275px">
@@ -153,21 +153,16 @@
create promotional materials for your application, such as screenshots, videos, graphics, and
promotional text.</p>
</li>
- <li>Planning publishing options.
+ <li>Configuring options and uploading assets.
<p>Android Market lets you target your application to a worldwide pool of users and devices.
- Using various Android Market tools, you can choose the countries you want to reach, the
- price you want to charge in each country, and the devices you want to target. You can also
- use Android Market's filtering settings to target specific device features and capabilities.</p>
- </li>
- <li>Configuring publishing options and uploading assets.
- <p>After you create your promotional materials and determine which publishing options are
- suitable for your application, you can use the Android Market developer console to configure
- those options and upload the promotional materials. You can also use the developer console to
- upload your application as a draft (unpublished) application, which lets you do final
- testing before you publish it for final release.</p>
+ By configuring various Android Market settings, you can choose the countries you want to
+ reach, the listing languages you want to use, and the price you want to charge in each
+ country. You can also configure listing details such as the application type, category, and
+ content rating. When you are done configuring options you can upload your promotional materials
+ and your application as a draft (unpublished) application.</p>
</li>
<li>Publishing the release version of your application.
- <p>When you are satisfied that your publishing settings are correctly configured and your
+ <p>If you are satisfied that your publishing settings are correctly configured and your
uploaded application is ready to be released to the public, you can simply click
<strong>Publish</strong > in the developer console and within minutes your application will be
live and available for download around the world.</p>
@@ -233,4 +228,4 @@
<p>Releasing applications through email is convenient if you are sending your application to
only a few trusted users, but it provides few protections from piracy and unauthorized
distribution; that is, anyone you send your application to can simply forward it to someone else.
-else.
\ No newline at end of file
+else.
diff --git a/docs/html/guide/topics/graphics/hardware-accel.jd b/docs/html/guide/topics/graphics/hardware-accel.jd
index e3ff215..39ccbf4 100644
--- a/docs/html/guide/topics/graphics/hardware-accel.jd
+++ b/docs/html/guide/topics/graphics/hardware-accel.jd
@@ -300,6 +300,16 @@
<li>{@link android.graphics.Paint#setRasterizer setRasterizer()}</li>
</ul>
</li>
+
+ <li>
+ <strong>Xfermodes</strong>
+
+ <ul>
+ <li>{@link android.graphics.AvoidXfermode AvoidXfermode}</li>
+
+ <li>{@link android.graphics.PixelXorXfermode PixelXorXfermode}</li>
+ </ul>
+ </li>
</ul>
<p>In addition, some operations behave differently with hardware acceleration enabled:</p>
@@ -315,9 +325,6 @@
<li>{@link android.graphics.Canvas#drawBitmapMesh drawBitmapMesh()}: colors array is
ignored</li>
-
- <li>{@link android.graphics.Canvas#setDrawFilter setDrawFilter()}: can be set, but is
- ignored</li>
</ul>
</li>
@@ -336,6 +343,24 @@
</li>
<li>
+ <strong>PorterDuffXfermode</strong>
+
+ <ul>
+ <li>{@link android.graphics.PorterDuff.Mode#DARKEN PorterDuff.Mode.DARKEN} will
+ be equivalent to {@link android.graphics.PorterDuff.Mode#SRC_OVER} when blending
+ against the framebuffer.</li>
+
+ <li>{@link android.graphics.PorterDuff.Mode#LIGHTEN PorterDuff.Mode.LIGHTEN} will
+ be equivalent to {@link android.graphics.PorterDuff.Mode#SRC_OVER} when blending
+ against the framebuffer.</li>
+
+ <li>{@link android.graphics.PorterDuff.Mode#OVERLAY PorterDuff.Mode.OVERLAY} will
+ be equivalent to {@link android.graphics.PorterDuff.Mode#SRC_OVER} when blending
+ against the framebuffer.</li>
+ </ul>
+ </li>
+
+ <li>
<strong>ComposeShader</strong>
<ul>
diff --git a/docs/html/guide/topics/providers/content-provider-basics.jd b/docs/html/guide/topics/providers/content-provider-basics.jd
new file mode 100644
index 0000000..40b5c3f
--- /dev/null
+++ b/docs/html/guide/topics/providers/content-provider-basics.jd
@@ -0,0 +1,1215 @@
+page.title=Content Provider Basics
+@jd:body
+<div id="qv-wrapper">
+<div id="qv">
+
+
+ <!-- In this document -->
+<h2>In this document</h2>
+<ol>
+ <li>
+ <a href="#Basics">Overview</a>
+ <ol>
+ <li>
+ <a href="#ClientProvider">Accessing a provider</a>
+ </li>
+ <li>
+ <a href="#ContentURIs">Content URIs</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#SimpleQuery">Retrieving Data from the Provider</a>
+ <ol>
+ <li>
+ <a href="#RequestPermissions">Requesting read access permission</a>
+ </li>
+ <li>
+ <a href="#Query">Constructing the query</a>
+ </li>
+ <li>
+ <a href="#DisplayResults">Displaying query results</a>
+ </li>
+ <li>
+ <a href="#GettingResults">Getting data from query results</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#Permissions">Content Provider Permissions</a>
+ </li>
+ <li>
+ <a href="#Modifications">Inserting, Updating, and Deleting Data</a>
+ <ol>
+ <li>
+ <a href="#Inserting">Inserting data</a>
+ </li>
+ <li>
+ <a href="#Updating">Updating data</a>
+ </li>
+ <li>
+ <a href="#Deleting">Deleting data</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#DataTypes">Provider Data Types</a>
+ </li>
+ <li>
+ <a href="#AltForms">Alternative Forms of Provider Access</a>
+ <ol>
+ <li>
+ <a href="#Batch">Batch access</a>
+ </li>
+ <li>
+ <a href="#Intents">Data access via intents</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#ContractClasses">Contract Classes</a>
+ </li>
+ <li>
+ <a href="#MIMETypeReference">MIME Type Reference</a>
+ </li>
+</ol>
+
+ <!-- Key Classes -->
+<h2>Key classes</h2>
+ <ol>
+ <li>
+ {@link android.content.ContentProvider}
+ </li>
+ <li>
+ {@link android.content.ContentResolver}
+ </li>
+ <li>
+ {@link android.database.Cursor}
+ </li>
+ <li>
+ {@link android.net.Uri}
+ </li>
+ </ol>
+
+ <!-- Related Samples -->
+<h2>Related Samples</h2>
+ <ol>
+ <li>
+ <a
+ href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html">
+ Cursor (People)</a>
+ </li>
+ <li>
+ <a
+ href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html">
+ Cursor (Phones)</a>
+ </li>
+ </ol>
+
+ <!-- See also -->
+<h2>See also</h2>
+ <ol>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-creating.html">
+ Creating a Content Provider</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/calendar-provider.html">
+ Calendar Provider</a>
+ </li>
+ </ol>
+</div>
+</div>
+
+ <!-- Intro paragraphs -->
+<p>
+ A content provider manages access to a central repository of data. The provider and
+ is part of an Android application, which often provides its own UI for working with
+ the data. However, content providers are primarily intended to be used by other
+ applications, which access the provider using a provider client object. Together, providers
+ and provider clients offer a consistent, standard interface to data that also handles
+ inter-process communication and secure data access.
+</p>
+<p>
+ This topic describes the basics of the following:
+</p>
+ <ul>
+ <li>How content providers work.</li>
+ <li>The API you use retrieve data from a content provider.</li>
+ <li>The API you use to insert, update, or delete data in a content provider.</li>
+ <li>Other API features that facilitate working with providers.</li>
+ </ul>
+
+ <!-- Basics -->
+<h2 id="Basics">Overview</h2>
+<p>
+ A content provider presents data to external applications as one or more tables that are
+ similar to the tables found in a relational database. A row represents an instance of some type
+ of data the provider collects, and each row in the column represents an individual piece of
+ data collected for an instance.
+</p>
+<p>
+ For example, one of the built-in providers in the Android platform is the user dictionary, which
+ stores the spellings of non-standard words that the user wants to keep. Table 1 illustrates what
+ the data might look like in this provider's table:
+</p>
+<p class="table-caption">
+ <strong>Table 1:</strong> Sample user dictionary table.
+</p>
+<table id="table1" style="width: 50%;">
+ <tr>
+ <th style="width:20%" align="center" scope="col">word</th>
+ <th style="width:20%" align="center" scope="col">app id</th>
+ <th style="width:20%" align="center" scope="col">frequency</th>
+ <th style="width:20%" align="center" scope="col">locale</th>
+ <th style="width:20%" align="center" scope="col">_ID</th>
+ </tr>
+ <tr>
+ <td align="center" scope="row">mapreduce</td>
+ <td align="center">user1</td>
+ <td align="center">100</td>
+ <td align="center">en_US</td>
+ <td align="center">1</td>
+ </tr>
+ <tr>
+ <td align="center" scope="row">precompiler</td>
+ <td align="center">user14</td>
+ <td align="center">200</td>
+ <td align="center">fr_FR</td>
+ <td align="center">2</td>
+ </tr>
+ <tr>
+ <td align="center" scope="row">applet</td>
+ <td align="center">user2</td>
+ <td align="center">225</td>
+ <td align="center">fr_CA</td>
+ <td align="center">3</td>
+ </tr>
+ <tr>
+ <td align="center" scope="row">const</td>
+ <td align="center">user1</td>
+ <td align="center">255</td>
+ <td align="center">pt_BR</td>
+ <td align="center">4</td>
+ </tr>
+ <tr>
+ <td align="center" scope="row">int</td>
+ <td align="center">user5</td>
+ <td align="center">100</td>
+ <td align="center">en_UK</td>
+ <td align="center">5</td>
+ </tr>
+</table>
+<p>
+ In table 1, each row represents an instance of a word that might not be
+ found in a standard dictionary. Each column represents some data for that word, such as the
+ locale in which it was first encountered. The column headers are column names that are stored in
+ the provider. To refer to a row's locale, you refer to its <code>locale</code> column. For
+ this provider, the <code>_ID</code> column serves as a "primary key" column that
+ the provider automatically maintains.
+</p>
+<p class="note">
+ <strong>Note:</strong> A provider isn't required to have a primary key, and it isn't required
+ to use <code>_ID</code> as the column name of a primary key if one is present. However,
+ if you want to bind data from a provider to a {@link android.widget.ListView}, one of the
+ column names has to be <code>_ID</code>. This requirement is explained in more detail in the
+ section <a href="#DisplayResults">Displaying query results</a>.
+</p>
+<h3 id="ClientProvider">Accessing a provider</h3>
+<p>
+ An application accesses the data from a content provider with
+ a {@link android.content.ContentResolver} client object. This object has methods that call
+ identically-named methods in the provider object, an instance of one of the concrete
+ subclasses of {@link android.content.ContentProvider}. The
+ {@link android.content.ContentResolver} methods provide the basic
+ "CRUD" (create, retrieve, update, and delete) functions of persistent storage.
+</p>
+<p>
+ The {@link android.content.ContentResolver} object in the client application's
+ process and the {@link android.content.ContentProvider} object in the application that owns
+ the provider automatically handle inter-process communication.
+ {@link android.content.ContentProvider} also acts as an abstraction layer between its
+ repository of data and the external appearance of data as tables.
+</p>
+<p class="note">
+ <strong>Note:</strong> To access a provider, your application usually has to request specific
+ permissions in its manifest file. This is described in more detail in the section
+ <a href="#Permissions">Content Provider Permissions</a>
+</p>
+<p>
+ For example, to get a list of the words and their locales from the User Dictionary Provider,
+ you call {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
+ ContentResolver.query()}.
+ The {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
+ query()} method calls the
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String)
+ ContentProvider.query()} method defined by the User Dictionary Provider. The following lines
+ of code show a
+ {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
+ ContentResolver.query()} call:
+<p>
+<pre>
+// Queries the user dictionary and returns results
+mCursor = getContentResolver().query(
+ UserDictionary.Words.CONTENT_URI, // The content URI of the words table
+ mProjection, // The columns to return for each row
+ mSelectionClause // Selection criteria
+ mSelectionArgs, // Selection criteria
+ mSortOrder); // The sort order for the returned rows
+</pre>
+<p>
+ Table 2 shows how the arguments to
+ {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
+ query(Uri,projection,selection,selectionArgs,sortOrder)} match an SQL SELECT statement:
+</p>
+<p class="table-caption">
+ <strong>Table 2:</strong> Query() compared to SQL query.
+</p>
+<table id="table2" style="width: 75%;">
+ <tr>
+ <th style="width:25%" align="center" scope="col">query() argument</th>
+ <th style="width:25%" align="center" scope="col">SELECT keyword/parameter</th>
+ <th style="width:50%" align="center" scope="col">Notes</th>
+ </tr>
+ <tr>
+ <td align="center"><code>Uri</code></td>
+ <td align="center"><code>FROM <em>table_name</em></code></td>
+ <td><code>Uri</code> maps to the table in the provider named <em>table_name</em>.</td>
+ </tr>
+ <tr>
+ <td align="center"><code>projection</code></td>
+ <td align="center"><code><em>col,col,col,...</em></code></td>
+ <td>
+ <code>projection</code> is an array of columns that should be included for each row
+ retrieved.
+ </td>
+ </tr>
+ <tr>
+ <td align="center"><code>selection</code></td>
+ <td align="center"><code>WHERE <em>col</em> = <em>value</em></code></td>
+ <td><code>selection</code> specifies the criteria for selecting rows.</td>
+ </tr>
+ <tr>
+ <td align="center"><code>selectionArgs</code></td>
+ <td align="center">
+ (No exact equivalent. Selection arguments replace <code>?</code> placeholders in the
+ selection clause.)
+ </td>
+ </tr>
+ <tr>
+ <td align="center"><code>sortOrder</code></td>
+ <td align="center"><code>ORDER BY <em>col,col,...</em></code></td>
+ <td>
+ <code>sortOrder</code> specifies the order in which rows appear in the returned
+ {@link android.database.Cursor}.
+ </td>
+ </tr>
+</table>
+<h3 id="ContentURIs">Content URIs</h3>
+<p>
+ A <strong>content URI</strong> is a URI that identifies data in a provider. Content URIs
+ include the symbolic name of the entire provider (its <strong>authority</strong>) and a
+ name that points to a table (a <strong>path</strong>). When you call
+ a client method to access a table in a provider, the content URI for the table is one of
+ the arguments.
+</p>
+<p>
+ In the preceding lines of code, the constant
+ {@link android.provider.UserDictionary.Words#CONTENT_URI} contains the content URI of
+ the user dictionary's "words" table. The {@link android.content.ContentResolver}
+ object parses out the URI's authority, and uses it to "resolve" the provider by
+ comparing the authority to a system table of known providers. The
+ {@link android.content.ContentResolver} can then dispatch the query arguments to the correct
+ provider.
+</p>
+<p>
+ The {@link android.content.ContentProvider} uses the path part of the content URI to choose the
+ table to access. A provider usually has a <strong>path</strong> for each table it exposes.
+</p>
+<p>
+ In the previous lines of code, the full URI for the "words" table is:
+</p>
+<pre>
+content://user_dictionary/words
+</pre>
+<p>
+ where the <code>user_dictionary</code> string is the provider's authority, and
+ <code>words</code> string is the table's path. The string
+ <code>content://</code> (the <strong>scheme</strong>) is always present,
+ and identifies this as a content URI.
+</p>
+<p>
+ Many providers allow you to access a single row in a table by appending an ID value
+ to the end of the URI. For example, to retrieve a row whose <code>_ID</code> is
+ <code>4</code> from user dictionary, you can use this content URI:
+</p>
+<pre>
+Uri singleUri = ContentUri.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
+</pre>
+<p>
+ You often use id values when you've retrieved a set of rows and then want to update or delete
+ one of them.
+</p>
+<p class="note">
+ <strong>Note:</strong> The {@link android.net.Uri} and {@link android.net.Uri.Builder} classes
+ contain convenience methods for constructing well-formed Uri objects from strings. The
+ {@link android.content.ContentUris} contains convenience methods for appending id values to
+ a URI. The previous snippet uses {@link android.content.ContentUris#withAppendedId(Uri, long)
+ withAppendedId()} to append an id to the UserDictionary content URI.
+</p>
+
+
+ <!-- Retrieving Data from the Provider -->
+<h2 id="SimpleQuery">Retrieving Data from the Provider</h2>
+<p>
+ This section describes how to retrieve data from a provider, using the User Dictionary Provider
+ as an example.
+</p>
+<p class="note">
+ For the sake of clarity, the code snippets in this section call
+ {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
+ ContentResolver.query()} on the "UI thread"". In actual code, however, you should
+ do queries asynchronously on a separate thread. One way to do this is to use the
+ {@link android.content.CursorLoader} class, which is described
+ in more detail in the <a href="{@docRoot}guide/topics/fundamentals/loaders.html">
+ Loaders</a> guide. Also, the lines of code are snippets only; they don't show a complete
+ application.
+</p>
+<p>
+ To retrieve data from a provider, follow these basic steps:
+</p>
+<ol>
+ <li>
+ Request the read access permission for the provider.
+ </li>
+ <li>
+ Define the code that sends a query to the provider.
+ </li>
+</ol>
+<h3 id="RequestPermissions">Requesting read access permission</h3>
+<p>
+ To retrieve data from a provider, your application needs "read access permission" for the
+ provider. You can't request this permission at run-time; instead, you have to specify that
+ you need this permission in your manifest, using the
+ <code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">
+ <uses-permission></a></code> element and the exact permission name defined by the
+ provider. When you specify this element in your manifest, you are in effect "requesting" this
+ permission for your application. When users install your application, they implicitly grant
+ this request.
+</p>
+<p>
+ To find the exact name of the read access permission for the provider you're using, as well
+ as the names for other access permissions used by the provider, look in the provider's
+ documentation.
+</p>
+<p>
+ The role of permissions in accessing providers is described in more detail in the section
+ <a href="#Permissions">Content Provider Permissions</a>.
+</p>
+<p>
+ The User Dictionary Provider defines the permission
+ <code>android.permission.READ_USER_DICTIONARY</code> in its manifest file, so an
+ application that wants to read from the provider must request this permission.
+</p>
+<!-- Constructing the query -->
+<h3 id="Query">Constructing the query</h3>
+<p>
+ The next step in retrieving data a provider is to construct a query. This first snippet
+ defines some variables for accessing the User Dictionary Provider:
+</p>
+<pre class="prettyprint">
+
+// A "projection" defines the columns that will be returned for each row
+String[] mProjection =
+{
+ UserDictionary.Words._ID, // Contract class constant for the _ID column name
+ UserDictionary.Words.WORD, // Contract class constant for the word column name
+ UserDictionary.Words.LOCALE // Contract class constant for the locale column name
+};
+
+// Defines a string to contain the selection clause
+String mSelectionClause = null;
+
+// Initializes an array to contain selection arguments
+String[] mSelectionArgs = {""};
+
+</pre>
+<p>
+ The next snippet shows how to use
+ {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
+ ContentResolver.query()}, using the User Dictionary Provider as an example.
+ A provider client query is similar to an SQL query, and it contains a set of columns to return,
+ a set of selection criteria, and a sort order.
+</p>
+<p>
+ The set of columns that the query should return is called a <strong>projection</strong>
+ (the variable <code>mProjection</code>).
+</p>
+<p>
+ The expression that specifies the rows to retrieve is split into a selection clause and
+ selection arguments. The selection clause is a combination of logical and Boolean expressions,
+ column names, and values (the variable <code>mSelection</code>). If you specify the replaceable
+ parameter <code>?</code> instead of a value, the query method retrieves the value from the
+ selection arguments array (the variable <code>mSelectionArgs</code>).
+</p>
+<p>
+ In the next snippet, if the user doesn't enter a word, the selection clause is set to
+ <code>null</code>, and the query returns all the words in the provider. If the user enters
+ a word, the selection clause is set to <code>UserDictionary.Words.Word + " = ?"</code> and
+ the first element of selection arguments array is set to the word the user enters.
+</p>
+<pre class="prettyprint">
+/*
+ * This defines a one-element String array to contain the selection argument.
+ */
+String[] mSelectionArgs = {""};
+
+// Gets a word from the UI
+mSearchString = mSearchWord.getText().toString();
+
+// Remember to insert code here to check for invalid or malicious input.
+
+// If the word is the empty string, gets everything
+if (TextUtils.isEmpty(mSearchString)) {
+ // Setting the selection clause to null will return all words
+ mSelectionClause = null;
+ mSelectionArgs[0] = "";
+
+} else {
+ // Constructs a selection clause that matches the word that the user entered.
+ mSelectionClause = " = ?";
+
+ // Moves the user's input string to the selection arguments.
+ mSelectionArgs[0] = mSearchString;
+
+}
+
+// Does a query against the table and returns a Cursor object
+mCursor = getContentResolver().query(
+ UserDictionary.Words.CONTENT_URI, // The content URI of the words table
+ mProjection, // The columns to return for each row
+ mSelectionClause // Either null, or the word the user entered
+ mSelectionArgs, // Either empty, or the string the user entered
+ mSortOrder); // The sort order for the returned rows
+
+// Some providers return null if an error occurs, others throw an exception
+if (null == mCursor) {
+ /*
+ * Insert code here to handle the error. Be sure not to use the cursor! You may want to
+ * call android.util.Log.e() to log this error.
+ *
+ */
+// If the Cursor is empty, the provider found no matches
+} else if (mCursor.getCount() < 1) {
+
+ /*
+ * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
+ * an error. You may want to offer the user the option to insert a new row, or re-type the
+ * search term.
+ */
+
+} else {
+ // Insert code here to do something with the results
+
+}
+</pre>
+<p>
+ This query is analogous to the SQL statement:
+</p>
+<pre>
+SELECT _ID, word, frequency, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
+</pre>
+<p>
+ In this SQL statement, the actual column names are used instead of contract class constants.
+</p>
+<h4 id="Injection">Protecting against malicious input</h4>
+<p>
+ If the data managed by the content provider is in an SQL database, including external untrusted
+ data into raw SQL statements can lead to SQL injection.
+</p>
+<p>
+ Consider this selection clause:
+</p>
+<pre>
+// Constructs a selection clause by concatenating the user's input to the column name
+String mSelectionClause = "var = " + mUserInput;
+</pre>
+<p>
+ If you do this, you're allowing the user to concatenate malicious SQL onto your SQL statement.
+ For example, the user could enter "nothing; DROP TABLE *;" for <code>mUserInput</code>, which
+ would result in the selection clause <code>var = nothing; DROP TABLE *;</code>. Since the
+ selection clause is treated as an SQL statement, this might cause the provider to erase all of
+ the tables in the underlying SQLite database (unless the provider is set up to catch
+ <a href="http://en.wikipedia.org/wiki/SQL_injection">SQL injection</a> attempts).
+</p>
+<p>
+ To avoid this problem, use a selection clause that uses <code>?</code> as a replaceable
+ parameter and a separate array of selection arguments. When you do this, the user input
+ is bound directly to the query rather than being interpreted as part of an SQL statement.
+ Because it's not treated as SQL, the user input can't inject malicious SQL. Instead of using
+ concatenation to include the user input, use this selection clause:
+</p>
+<pre>
+// Constructs a selection clause with a replaceable parameter
+String mSelectionClause = "var = ?";
+</pre>
+<p>
+ Set up the array of selection arguments like this:
+</p>
+<pre>
+// Defines an array to contain the selection arguments
+String[] selectionArgs = {""};
+</pre>
+<p>
+ Put a value in the selection arguments array like this:
+</p>
+<pre>
+// Sets the selection argument to the user's input
+selectionArgs[0] = mUserInput;
+</pre>
+<p>
+ A selection clause that uses <code>?</code> as a replaceable parameter and an array of
+ selection arguments array are preferred way to specify a selection, even the provider isn't
+ based on an SQL database.
+</p>
+<!-- Displaying the results -->
+<h3 id="DisplayResults">Displaying query results</h3>
+<p>
+ The {@link android.content.ContentResolver#query(Uri, String[], String, String[], String)
+ ContentResolver.query()} client method always returns a {@link android.database.Cursor}
+ containing the columns specified by the query's projection for the rows that match the query's
+ selection criteria. A {@link android.database.Cursor} object provides random read access to the
+ rows and columns it contains. Using {@link android.database.Cursor} methods,
+ you can iterate over the rows in the results, determine the data type of each column, get the
+ data out of a column, and examine other properties of the results. Some
+ {@link android.database.Cursor} implementations automatically update the object when the
+ provider's data changes, or trigger methods in an observer object when the
+ {@link android.database.Cursor} changes, or both.
+</p>
+<p class="note">
+ <strong>Note:</strong> A provider may restrict access to columns based on the nature of the
+ object making the query. For example, the Contacts Provider restricts access for some columns to
+ sync adapters, so it won't return them to an activity or service.
+</p>
+<p>
+ If no rows match the selection criteria, the provider
+ returns a {@link android.database.Cursor} object for which
+ {@link android.database.Cursor#getCount() Cursor.getCount()} is 0 (an empty cursor).
+</p>
+<p>
+ If an internal error occurs, the results of the query depend on the particular provider. It may
+ choose to return <code>null</code>, or it may throw an {@link java.lang.Exception}.
+</p>
+<p>
+ Since a {@link android.database.Cursor} is a "list" of rows, a good way to display the
+ contents of a {@link android.database.Cursor} is to link it to a {@link android.widget.ListView}
+ via a {@link android.widget.SimpleCursorAdapter}.
+</p>
+<p>
+ The following snippet continues the code from the previous snippet. It creates a
+ {@link android.widget.SimpleCursorAdapter} object containing the {@link android.database.Cursor}
+ retrieved by the query, and sets this object to be the adapter for a
+ {@link android.widget.ListView}:
+</p>
+<pre class="prettyprint">
+// Defines a list of columns to retrieve from the Cursor and load into an output row
+String[] mWordListColumns =
+{
+ UserDictionary.Words.WORD, // Contract class constant containing the word column name
+ UserDictionary.Words.LOCALE // Contract class constant containing the locale column name
+};
+
+// Defines a list of View IDs that will receive the Cursor columns for each row
+int[] mWordListItems = { R.id.dictWord, R.id.locale};
+
+// Creates a new SimpleCursorAdapter
+mCursorAdapter = new SimpleCursorAdapter(
+ getApplicationContext(), // The application's Context object
+ R.layout.wordlistrow, // A layout in XML for one row in the ListView
+ mCursor, // The result from the query
+ mWordListColumns, // A string array of column names in the cursor
+ mWordListItems, // An integer array of view IDs in the row layout
+ 0); // Flags (usually none are needed)
+
+// Sets the adapter for the ListView
+mWordList.setAdapter(mCursorAdapter);
+</pre>
+<p class="note">
+ <strong>Note:</strong> To back a {@link android.widget.ListView} with a
+ {@link android.database.Cursor}, the cursor must contain a column named <code>_ID</code>.
+ Because of this, the query shown previously retrieves the <code>_ID</code> column for the
+ "words" table, even though the {@link android.widget.ListView} doesn't display it.
+ This restriction also explains why most providers have a <code>_ID</code> column for each of
+ their tables.
+</p>
+
+ <!-- Getting data from query results -->
+<h3 id="GettingResults">Getting data from query results</h3>
+<p>
+ Rather than simply displaying query results, you can use them for other tasks. For
+ example, you can retrieve spellings from the user dictionary and then look them up in
+ other providers. To do this, you iterate over the rows in the {@link android.database.Cursor}:
+</p>
+<pre class="prettyprint">
+
+// Determine the column index of the column named "word"
+int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
+
+/*
+ * Only executes if the cursor is valid. The User Dictionary Provider returns null if
+ * an internal error occurs. Other providers may throw an Exception instead of returning null.
+ */
+
+if (mCursor != null) {
+ /*
+ * Moves to the next row in the cursor. Before the first movement in the cursor, the
+ * "row pointer" is -1, and if you try to retrieve data at that position you will get an
+ * exception.
+ */
+ while (mCursor.moveToNext()) {
+
+ // Gets the value from the column.
+ newWord = mCursor.getString(index);
+
+ // Insert code here to process the retrieved word.
+
+ ...
+
+ // end of while loop
+ }
+} else {
+
+ // Insert code here to report an error if the cursor is null or the provider threw an exception.
+}
+</pre>
+<p>
+ {@link android.database.Cursor} implementations contain several "get" methods for
+ retrieving different types of data from the object. For example, the previous snippet
+ uses {@link android.database.Cursor#getString(int) getString()}. They also have a
+ {@link android.database.Cursor#getType(int) getType()} method that returns a value indicating
+ the data type of the column.
+</p>
+
+
+ <!-- Requesting permissions -->
+<h2 id="Permissions">Content Provider Permissions</h2>
+<p>
+ A provider's application can specify permissions that other applications must have in order to
+ access the provider's data. These permissions ensure that the user knows what data
+ an application will try to access. Based on the provider's requirements, other applications
+ request the permissions they need in order to access the provider. End users see the requested
+ permissions when they install the application.
+</p>
+<p>
+ If a provider's application doesn't specify any permissions, then other applications have no
+ access to the provider's data. However, components in the provider's application always have
+ full read and write access, regardless of the specified permissions.
+</p>
+<p>
+ As noted previously, the User Dictionary Provider requires the
+ <code>android.permission.READ_USER_DICTIONARY</code> permission to retrieve data from it.
+ The provider has the separate <code>android.permission.WRITE_USER_DICTIONARY</code>
+ permission for inserting, updating, or deleting data.
+</p>
+<p>
+ To get the permissions needed to access a provider, an application requests them with a
+ <code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">
+ <uses-permission></a></code> element in its manifest file.
+ When the Android Package Manager installs the application, a user must approve all of the
+ permissions the application requests. If the user approves all of them, Package Manager
+ continues the installation; if the user doesn't approve them, Package Manager
+ aborts the installation.
+</p>
+<p>
+ The following
+ <code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">
+ <uses-permission></a></code> element requests read access to the User Dictionary Provider:
+</p>
+<pre>
+ <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
+</pre>
+<p>
+ The impact of permissions on provider access is explained in more detail in the
+ <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> guide.
+</p>
+
+
+<!-- Inserting, Updating, and Deleting Data -->
+<h2 id="Modifications">Inserting, Updating, and Deleting Data</h2>
+<p>
+ In the same way that you retrieve data from a provider, you also use the interaction between
+ a provider client and the provider's {@link android.content.ContentProvider} to modify data.
+ You call a method of {@link android.content.ContentResolver} with arguments that are passed to
+ the corresponding method of {@link android.content.ContentProvider}. The provider and provider
+ client automatically handle security and inter-process communication.
+</p>
+<h3 id="Inserting">Inserting data</h3>
+<p>
+ To insert data into a provider, you call the
+ {@link android.content.ContentResolver#insert(Uri,ContentValues) ContentResolver.insert()}
+ method. This method inserts a new row into the provider and returns a content URI for that row.
+ This snippet shows how to insert a new word into the User Dictionary Provider:
+</p>
+<pre class="prettyprint">
+// Defines a new Uri object that receives the result of the insertion
+Uri mNewUri;
+
+...
+
+// Defines an object to contain the new values to insert
+ContentValues mNewValues = new ContentValues();
+
+/*
+ * Sets the values of each column and inserts the word. The arguments to the "put"
+ * method are "column name" and "value"
+ */
+mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
+mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
+mNewValues.put(UserDictionary.Words.WORD, "insert");
+mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
+
+mNewUri = getContentResolver().insert(
+ UserDictionary.Word.CONTENT_URI, // the user dictionary content URI
+ mNewValues // the values to insert
+);
+</pre>
+<p>
+ The data for the new row goes into a single {@link android.content.ContentValues} object, which
+ is similar in form to a one-row cursor. The columns in this object don't need to have the
+ same data type, and if you don't want to specify a value at all, you can set a column
+ to <code>null</code> using {@link android.content.ContentValues#putNull(String)
+ ContentValues.putNull()}.
+</p>
+<p>
+ The snippet doesn't add the <code>_ID</code> column, because this column is maintained
+ automatically. The provider assigns a unique value of <code>_ID</code> to every row that is
+ added. Providers usually use this value as the table's primary key.
+</p>
+<p>
+ The content URI returned in <code>newUri</code> identifies the newly-added row, with
+ the following format:
+</p>
+<pre>
+content://user_dictionary/words/<id_value>
+</pre>
+<p>
+ The <code><id_value></code> is the contents of <code>_ID</code> for the new row.
+ Most providers can detect this form of content URI automatically and then perform the requested
+ operation on that particular row.
+</p>
+<p>
+ To get the value of <code>_ID</code> from the returned {@link android.net.Uri}, call
+ {@link android.content.ContentUris#parseId(Uri) ContentUris.parseId()}.
+</p>
+<h3 id="Updating">Updating data</h3>
+<p>
+ To update a row, you use a {@link android.content.ContentValues} object with the updated
+ values just as you do with an insertion, and selection criteria just as you do with a query.
+ The client method you use is
+ {@link android.content.ContentResolver#update(Uri, ContentValues, String, String[])
+ ContentResolver.update()}. You only need to add values to the
+ {@link android.content.ContentValues} object for columns you're updating. If you want to clear
+ the contents of a column, set the value to <code>null</code>.
+</p>
+<p>
+ The following snippet changes all the rows whose locale has the language "en" to a
+ have a locale of <code>null</code>. The return value is the number of rows that were updated:
+</p>
+<pre>
+// Defines an object to contain the updated values
+ContentValues mUpdateValues = new ContentValues();
+
+// Defines selection criteria for the rows you want to update
+String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?";
+String[] mSelectionArgs = {"en_%"};
+
+// Defines a variable to contain the number of updated rows
+int mRowsUpdated = 0;
+
+...
+
+/*
+ * Sets the updated value and updates the selected words.
+ */
+mUpdateValues.putNull(UserDictionary.Words.LOCALE);
+
+mRowsUpdated = getContentResolver().update(
+ UserDictionary.Words.CONTENT_URI, // the user dictionary content URI
+ mUpdateValues // the columns to update
+ mSelectionClause // the column to select on
+ mSelectionArgs // the value to compare to
+);
+</pre>
+<p>
+ You should also sanitize user input when you call
+ {@link android.content.ContentResolver#update(Uri, ContentValues, String, String[])
+ ContentResolver.update()}. To learn more about this, read the section
+ <a href="#Injection">Protecting against malicious input</a>.
+</p>
+<h3 id="Deleting">Deleting data</h3>
+<p>
+ Deleting rows is similar to retrieving row data: you specify selection criteria for the rows
+ you want to delete and the client method returns the number of deleted rows.
+ The following snippet deletes rows whose appid matches "user". The method returns the
+ number of deleted rows.
+</p>
+<pre>
+
+// Defines selection criteria for the rows you want to delete
+String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
+String[] mSelectionArgs = {"user"};
+
+// Defines a variable to contain the number of rows deleted
+int mRowsDeleted = 0;
+
+...
+
+// Deletes the words that match the selection criteria
+mRowsDeleted = getContentResolver().delete(
+ UserDictionary.Words.CONTENT_URI, // the user dictionary content URI
+ mSelectionClause // the column to select on
+ mSelectionArgs // the value to compare to
+);
+</pre>
+<p>
+ You should also sanitize user input when you call
+ {@link android.content.ContentResolver#delete(Uri, String, String[])
+ ContentResolver.delete()}. To learn more about this, read the section
+ <a href="#Injection">Protecting against malicious input</a>.
+</p>
+<!-- Provider Data Types -->
+<h2 id="DataTypes">Provider Data Types</h2>
+<p>
+ Content providers can offer many different data types. The User Dictionary Provider offers only
+ text, but providers can also offer the following formats:
+</p>
+ <ul>
+ <li>
+ integer
+ </li>
+ <li>
+ long integer (long)
+ </li>
+ <li>
+ floating point
+ </li>
+ <li>
+ long floating point (double)
+ </li>
+ </ul>
+<p>
+ Another data type that providers often use is Binary Large OBject (BLOB) implemented as a
+ 64KB byte array. You can see the available data types by looking at the
+ {@link android.database.Cursor} class "get" methods.
+</p>
+<p>
+ The data type for each column in a provider is usually listed in its documentation.
+ The data types for the User Dictionary Provider are listed in the reference documentation
+ for its contract class {@link android.provider.UserDictionary.Words} (contract classes are
+ described in the section <a href="#ContractClasses">Contract Classes</a>).
+ You can also determine the data type by calling {@link android.database.Cursor#getType(int)
+ Cursor.getType()}.
+</p>
+<p>
+ Providers also maintain MIME data type information for each content URI they define. You can
+ use the MIME type information to find out if your application can handle data that the
+ provider offers, or to choose a type of handling based on the MIME type. You usually need the
+ MIME type when you are working with a provider that contains complex
+ data structures or files. For example, the {@link android.provider.ContactsContract.Data}
+ table in the Contacts Provider uses MIME types to label the type of contact data stored in each
+ row. To get the MIME type corresponding to a content URI, call
+ {@link android.content.ContentResolver#getType(Uri) ContentResolver.getType()}.
+</p>
+<p>
+ The section <a href="#MIMETypeReference">MIME Type Reference</a> describes the
+ syntax of both standard and custom MIME types.
+</p>
+
+
+<!-- Alternative Forms of Provider Access -->
+<h2 id="AltForms">Alternative Forms of Provider Access</h2>
+<p>
+ Three alternative forms of provider access are important in application development:
+</p>
+<ul>
+ <li>
+ <a href="#Batch">Batch access</a>: You can create a batch of access calls with methods in
+ the {@link android.content.ContentProviderOperation} class, and then apply them with
+ {@link android.content.ContentResolver#applyBatch(String, ArrayList)
+ ContentResolver.applyBatch()}.
+ </li>
+ <li>
+ Asynchronous queries: You should do queries in a separate thread. One way to do this is to
+ use a {@link android.content.CursorLoader} object. The examples in the
+ <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> guide demonstrate
+ how to do this.
+ </li>
+ <li>
+ <a href="#Intents">Data access via intents</a>: Although you can't send an intent
+ directly to a provider, you can send an intent to the provider's application, which is
+ usually the best-equipped to modify the provider's data.
+ </li>
+</ul>
+<p>
+ Batch access and modification via intents are described in the following sections.
+</p>
+<h3 id="Batch">Batch access</h3>
+<p>
+ Batch access to a provider is useful for inserting a large number of rows, or for inserting
+ rows in multiple tables in the same method call, or in general for performing a set of
+ operations across process boundaries as a transaction (an atomic operation).
+</p>
+<p>
+ To access a provider in "batch mode",
+ you create an array of {@link android.content.ContentProviderOperation} objects and then
+ dispatch them to a content provider with
+ {@link android.content.ContentResolver#applyBatch(String, ArrayList)
+ ContentResolver.applyBatch()}. You pass the content provider's <em>authority</em> to this
+ method, rather than a particular content URI, which allows each
+ {@link android.content.ContentProviderOperation} object in the array to work against a
+ different table. A call to {@link android.content.ContentResolver#applyBatch(String, ArrayList)
+ ContentResolver.applyBatch()} returns an array of results.
+</p>
+<p>
+ The description of the {@link android.provider.ContactsContract.RawContacts} contract class
+ includes a code snippet that demonstrates batch insertion. The
+ <a href="{@docRoot}resources/samples/ContactManager/index.html">Contact Manager</a>
+ sample application contains an example of batch access in its <code>ContactAdder.java</code>
+ source file.
+</p>
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h2>Displaying data using a helper app</h2>
+<p>
+ If your application <em>does</em> have access permissions, you still may want to use an
+ intent to display data in another application. For example, the Calendar application accepts an
+ {@link android.content.Intent#ACTION_VIEW} intent, which displays a particular date or event.
+ This allows you to display calendar information without having to create your own UI.
+ To learn more about this feature, see the
+ <a href="{@docRoot}guide/topics/providers/calendar-provider.html">Calendar Provider</a> guide.
+</p>
+<p>
+ The application to which you send the intent doesn't have to be the application
+ associated with the provider. For example, you can retrieve a contact from the
+ Contact Provider, then send an {@link android.content.Intent#ACTION_VIEW} intent
+ containing the content URI for the contact's image to an image viewer.
+</p>
+</div>
+</div>
+<h3 id="Intents">Data access via intents</h3>
+<p>
+ Intents can provide indirect access to a content provider. You allow the user to access
+ data in a provider even if your application doesn't have access permissions, either by
+ getting a result intent back from an application that has permissions, or by activating an
+ application that has permissions and letting the user do work in it.
+</p>
+<h4>Getting access with temporary permissions</h4>
+<p>
+ You can access data in a content provider, even if you don't have the proper access
+ permissions, by sending an intent to an application that does have the permissions and
+ receiving back a result intent containing "URI" permissions.
+ These are permissions for a specific content URI that last until the activity that receives
+ them is finished. The application that has permanent permissions grants temporary
+ permissions by setting a flag in the result intent:
+</p>
+<ul>
+ <li>
+ <strong>Read permission:</strong>
+ {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}
+ </li>
+ <li>
+ <strong>Write permission:</strong>
+ {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION}
+ </li>
+</ul>
+<p class="note">
+ <strong>Note:</strong> These flags don't give general read or write access to the provider
+ whose authority is contained in the content URI. The access is only for the URI itself.
+</p>
+<p>
+ A provider defines URI permissions for content URIs in its manifest, using the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn">
+ android:grantUriPermission</a></code>
+ attribute of the
+ {@code <a href="guide/topics/manifest/provider-element.html"><provider></a>}
+ element, as well as the
+ {@code <a href="guide/topics/manifest/grant-uri-permission-element.html">
+ <grant-uri-permission></a>} child element of the
+ {@code <a href="guide/topics/manifest/provider-element.html"><provider></a>}
+ element. The URI permissions mechanism is explained in more detail in the
+ <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> guide,
+ in the section "URI Permissions".
+</p>
+<p>
+ For example, you can retrieve data for a contact in the Contacts Provider, even if you don't
+ have the {@link android.Manifest.permission#READ_CONTACTS} permission. You might want to do
+ this in an application that sends e-greetings to a contact on his or her birthday. Instead of
+ requesting {@link android.Manifest.permission#READ_CONTACTS}, which gives you access to all of
+ the user's contacts and all of their information, you prefer to let the user control which
+ contacts are used by your application. To do this, you use the following process:
+</p>
+<ol>
+ <li>
+ Your application sends an intent containing the action
+ {@link android.content.Intent#ACTION_PICK} and the "contacts" MIME type
+ {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}, using the
+ method {@link android.app.Activity#startActivityForResult(Intent, int)
+ startActivityForResult()}.
+ </li>
+ <li>
+ Because this intent matches the intent filter for the
+ People app's "selection" activity, the activity will come to the foreground.
+ </li>
+ <li>
+ In the selection activity, the user selects a
+ contact to update. When this happens, the selection activity calls
+ {@link android.app.Activity#setResult(int, Intent) setResult(resultcode, intent)}
+ to set up a intent to give back to your application. The intent contains the content URI
+ of the contact the user selected, and the "extras" flags
+ {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}. These flags grant URI
+ permission to your app to read data for the contact pointed to by the
+ content URI. The selection activity then calls {@link android.app.Activity#finish()} to
+ return control to your application.
+ </li>
+ <li>
+ Your activity returns to the foreground, and the system calls your activity's
+ {@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()}
+ method. This method receives the result intent created by the selection activity in
+ the People app.
+ </li>
+ <li>
+ With the content URI from the result intent, you can read the contact's data
+ from the Contacts Provider, even though you didn't request permanent read access permission
+ to the provider in your manifest. You can then get the contact's birthday information
+ or his or her email address and then send the e-greeting.
+ </li>
+</ol>
+<h4>Using another application</h4>
+<p>
+ A simple way to allow the user to modify data to which you don't have access permissions is to
+ activate an application that has permissions and let the user do the work there.
+</p>
+<p>
+ For example, the Calendar application accepts an
+ {@link android.content.Intent#ACTION_INSERT} intent, which allows you to activate the
+ application's insert UI. You can pass "extras" data in this intent, which the application
+ uses to pre-populate the UI. Because recurring events have a complex syntax, the preferred
+ way of inserting events into the Calendar Provider is to activate the Calendar app with an
+ {@link android.content.Intent#ACTION_INSERT} and then let the user insert the event there.
+</p>
+<!-- Contract Classes -->
+<h2 id="ContractClasses">Contract Classes</h2>
+<p>
+ A contract class defines constants that help applications work with the content URIs, column
+ names, intent actions, and other features of a content provider. Contract classes are not
+ included automatically with a provider; the provider's developer has to define them and then
+ make them available to other developers. Many of the providers included with the Android
+ platform have corresponding contract classes in the package {@link android.provider}.
+</p>
+<p>
+ For example, the User Dictionary Provider has a contract class
+ {@link android.provider.UserDictionary} containing content URI and column name constants. The
+ content URI for the "words" table is defined in the constant
+ {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}.
+ The {@link android.provider.UserDictionary.Words} class also contains column name constants,
+ which are used in the example snippets in this guide. For example, a query projection can be
+ defined as:
+</p>
+<pre>
+String[] mProjection =
+{
+ UserDictionary.Words._ID,
+ UserDictionary.Words.WORD,
+ UserDictionary.Words.LOCALE
+};
+</pre>
+<p>
+ Another contract class is {@link android.provider.ContactsContract} for the Contacts Provider.
+ The reference documentation for this class includes example code snippets. One of its
+ subclasses, {@link android.provider.ContactsContract.Intents.Insert}, is a contract
+ class that contains constants for intents and intent data.
+</p>
+
+
+<!-- MIME Type Reference -->
+<h2 id="MIMETypeReference">MIME Type Reference</h2>
+<p>
+ Content providers can return standard MIME media types, or custom MIME type strings, or both.
+</p>
+<p>
+ MIME types have the format
+</p>
+<pre>
+<em>type</em>/<em>subtype</em>
+</pre>
+<p>
+ For example, the well-known MIME type <code>text/html</code> has the <code>text</code> type and
+ the <code>html</code> subtype. If the provider returns this type for a URI, it means that a
+ query using that URI will return text containing HTML tags.
+</p>
+<p>
+ Custom MIME type strings, also called "vendor-specific" MIME types, have more
+ complex <em>type</em> and <em>subtype</em> values. The <em>type</em> value is always
+</p>
+<pre>
+vnd.android.cursor.<strong>dir</strong>
+</pre>
+<p>
+ for multiple rows, or
+</p>
+<pre>
+vnd.android.cursor.<strong>item</strong>
+</pre>
+<p>
+ for a single row.
+</p>
+<p>
+ The <em>subtype</em> is provider-specific. The Android built-in providers usually have a simple
+ subtype. For example, the when the Contacts application creates a row for a telephone number,
+ it sets the following MIME type in the row:
+</p>
+<pre>
+vnd.android.cursor.item/phone_v2
+</pre>
+<p>
+ Notice that the subtype value is simply <code>phone_v2</code>.
+</p>
+<p>
+ Other provider developers may create their own pattern of subtypes based on the provider's
+ authority and table names. For example, consider a provider that contains train timetables.
+ The provider's authority is <code>com.example.trains</code>, and it contains the tables
+ Line1, Line2, and Line3. In response to the content URI
+</p>
+<p>
+<pre>
+content://com.example.trains/Line1
+</pre>
+<p>
+ for table Line1, the provider returns the MIME type
+</p>
+<pre>
+vnd.android.cursor.<strong>dir</strong>/vnd.example.line1
+</pre>
+<p>
+ In response to the content URI
+</p>
+<pre>
+content://com.example.trains/Line2/5
+</pre>
+<p>
+ for row 5 in table Line2, the provider returns the MIME type
+</p>
+<pre>
+vnd.android.cursor.<strong>item</strong>/vnd.example.line2
+</pre>
+<p>
+ Most content providers define contract class constants for the MIME types they use. The
+ Contacts Provider contract class {@link android.provider.ContactsContract.RawContacts},
+ for example, defines the constant
+ {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} for the MIME type of
+ a single raw contact row.
+</p>
+<p>
+ Content URIs for single rows are described in the section
+ <a href="#ContentURIs">Content URIs</a>.
+</p>
diff --git a/docs/html/guide/topics/providers/content-provider-creating.jd b/docs/html/guide/topics/providers/content-provider-creating.jd
new file mode 100644
index 0000000..4ebdb50
--- /dev/null
+++ b/docs/html/guide/topics/providers/content-provider-creating.jd
@@ -0,0 +1,1215 @@
+page.title=Creating a Content Provider
+@jd:body
+<div id="qv-wrapper">
+<div id="qv">
+
+
+<h2>In this document</h2>
+<ol>
+ <li>
+ <a href="#DataStorage">Designing Data Storage</a>
+ </li>
+ <li>
+ <a href="#ContentURI">Designing Content URIs</a>
+ </li>
+ <li>
+ <a href="#ContentProvider">Implementing the ContentProvider Class</a>
+ <ol>
+ <li>
+ <a href="#RequiredAccess">Required Methods</a>
+ </li>
+ <li>
+ <a href="#Query">Implementing the query() method</a>
+ </li>
+ <li>
+ <a href="#Insert">Implementing the insert() method</a>
+ </li>
+ <li>
+ <a href="#Delete">Implementing the delete() method</a>
+ </li>
+ <li>
+ <a href="#Update">Implementing the update() method</a>
+ </li>
+ <li>
+ <a href="#OnCreate">Implementing the onCreate() method</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#MIMETypes">Implementing Content Provider MIME Types</a>
+ <ol>
+ <li>
+ <a href="#TableMIMETypes">MIME types for tables</a>
+ </li>
+ <li>
+ <a href="#FileMIMETypes">MIME types for files</a>
+ </li>
+ </ol>
+ </li>
+ <li>
+ <a href="#ContractClass">Implementing a Contract Class</a>
+ </li>
+ <li>
+ <a href="#Permissions">Implementing Content Provider Permissions</a>
+ </li>
+ <li>
+ <a href="#ProviderElement">The <provider> Element</a>
+ </li>
+ <li>
+ <a href="#Intents">Intents and Data Access</a>
+ </li>
+</ol>
+<h2>Key classes</h2>
+ <ol>
+ <li>
+ {@link android.content.ContentProvider}
+ </li>
+ <li>
+ {@link android.database.Cursor}
+ </li>
+ <li>
+ {@link android.net.Uri}
+ </li>
+ </ol>
+<h2>Related Samples</h2>
+ <ol>
+ <li>
+ <a
+ href="{@docRoot}resources/samples/NotePad/index.html">
+ Note Pad sample application
+ </a>
+ </li>
+ </ol>
+<h2>See also</h2>
+ <ol>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/calendar-provider.html">
+ Calendar Provider</a>
+ </li>
+ </ol>
+</div>
+</div>
+
+
+<p>
+ A content provider manages access to a central repository of data. You implement a
+ provider as one or more classes in an Android application, along with elements in
+ the manifest file. One of your classes implements a subclass
+ {@link android.content.ContentProvider}, which is the interface between your provider and
+ other applications. Although content providers are meant to make data available to other
+ applications, you may of course have activities in your application that allow the user
+ to query and modify the data managed by your provider.
+</p>
+<p>
+ The rest of this topic is a basic list of steps for building a content provider and a list
+ of APIs to use.
+</p>
+
+
+<!-- Before You Start Building -->
+<h2 id="BeforeYouStart">Before You Start Building</h2>
+<p>
+ Before you start building a provider, do the following:
+</p>
+<ol>
+ <li>
+ <strong>Decide if you need a content provider</strong>. You need to build a content
+ provider if you want to provide one or more of the following features:
+ <ul>
+ <li>You want to offer complex data or files to other applications.</li>
+ <li>You want to allow users to copy complex data from your app into other apps.</li>
+ <li>You want to provide custom search suggestions using the search framework.</li>
+ </ul>
+ <p>
+ You <em>don't</em> need a provider to use an SQLite database if the use is entirely within
+ your own application.
+ </p>
+ </li>
+ <li>
+ If you haven't done so already, read the topic
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics</a> to learn more about providers.
+ </li>
+</ol>
+<p>
+ Next, follow these steps to build your provider:
+</p>
+<ol>
+ <li>
+ Design the raw storage for your data. A content provider offers data in two ways:
+ <dl>
+ <dt>
+ File data
+ </dt>
+ <dd>
+ Data that normally goes into files, such as
+ photos, audio, or videos. Store the files in your application's private
+ space. In response to a request for a file from another application, your
+ provider can offer a handle to the file.
+ </dd>
+ <dt>
+ "Structured" data
+ </dt>
+ <dd>
+ Data that normally goes into a database, array, or similar structure.
+ Store the data in a form that's compatible with tables of rows and columns. A row
+ represents an entity, such as a person or an item in inventory. A column represents
+ some data for the entity, such a person's name or an item's price. A common way to
+ store this type of data is in an SQLite database, but you can use any type of
+ persistent storage. To learn more about the storage types available in the
+ Android system, see the section <a href="#DataStorage">
+ Designing Data Storage</a>.
+ </dd>
+ </dl>
+ </li>
+ <li>
+ Define a concrete implementation of the {@link android.content.ContentProvider} class and
+ its required methods. This class is the interface between your data and the rest of the
+ Android system. For more information about this class, see the section
+ <a href="#ContentProvider">Implementing the ContentProvider Class</a>.
+ </li>
+ <li>
+ Define the provider's authority string, its content URIs, and column names. If you want
+ the provider's application to handle intents, also define intent actions, extras data,
+ and flags. Also define the permissions that you will require for applications that want
+ to access your data. You should consider defining all of these values as constants in a
+ separate contract class; later, you can expose this class to other developers. For more
+ information about content URIs, see the
+ section <a href="#ContentURI">Designing Content URIs</a>.
+ For more information about intents, see the
+ section <a href="#Intents">Intents and Data Access</a>.
+ </li>
+ <li>
+ Add other optional pieces, such as sample data or an implementation
+ of {@link android.content.AbstractThreadedSyncAdapter} that can synchronize data between
+ the provider and cloud-based data.
+ </li>
+</ol>
+
+
+<!-- Designing Data Storage -->
+<h2 id="DataStorage">Designing Data Storage</h2>
+<p>
+ A content provider is the interface to data saved in a structured format. Before you create
+ the interface, you must decide how to store the data. You can store the data in any form you
+ like, and then design the interface to read and write the data as necessary.
+</p>
+<p>
+ These are some of the data storage technologies that are available in Android:
+</p>
+<ul>
+ <li>
+ The Android system includes an SQLite database API that Android's own providers use
+ to store table-oriented data. The
+ {@link android.database.sqlite.SQLiteOpenHelper} class helps you create databases, and the
+ {@link android.database.sqlite.SQLiteDatabase} class is the base class for accessing
+ databases.
+ <p>
+ Remember that you don't have to use a database to implement your repository. A provider
+ appears externally as a set of tables, similar to a relational database, but this is
+ not a requirement for the provider's internal implementation.
+ </p>
+ </li>
+ <li>
+ For storing file data, Android has a variety of file-oriented APIs.
+ To learn more about file storage, read the topic
+ <a href="{@docRoot}guide/topics/data/data-storage.html">Data Storage</a>. If you're
+ designing a provider that offers media-related data such as music or videos, you can
+ have a provider that combines table data and files.
+ </li>
+ <li>
+ For working with network-based data, use classes in {@link java.net} and
+ {@link android.net}. You can also synchronize network-based data to a local data
+ store such as a database, and then offer the data as tables or files.
+ The <a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">
+ Sample Sync Adapter</a> sample application demonstrates this type of synchronization.
+ </li>
+</ul>
+<h3 id="DataDesign">
+ Data design considerations
+</h3>
+<p>
+ Here are some tips for designing your provider's data structure:
+</p>
+<ul>
+ <li>
+ Table data should always have a "primary key" column that the provider maintains
+ as a unique numeric value for each row. You can use this value to link the row to related
+ rows in other tables (using it as a "foreign key"). Although you can use any name
+ for this column, using {@link android.provider.BaseColumns#_ID BaseColumns._ID} is the best
+ choice, because linking the results of a provider query to a
+ {@link android.widget.ListView} requires one of the retrieved columns to have the name
+ <code>_ID</code>.
+ </li>
+ <li>
+ If you want to provide bitmap images or other very large pieces of file-oriented data, store
+ the data in a file and then provide it indirectly rather than storing it directly in a
+ table. If you do this, you need to tell users of your provider that they need to use a
+ {@link android.content.ContentResolver} file method to access the data.
+ </li>
+ <li>
+ Use the Binary Large OBject (BLOB) data type to store data that varies in size or has a
+ varying structure. For example, you can use a BLOB column to store a
+ <a href="http://code.google.com/p/protobuf">protocol buffer</a> or
+ <a href="http://www.json.org">JSON structure</a>.
+ <p>
+ You can also use a BLOB to implement a <em>schema-independent</em> table. In
+ this type of table, you define a primary key column, a MIME type column, and one or
+ more generic columns as BLOB. The meaning of the data in the BLOB columns is indicated
+ by the value in the MIME type column. This allows you to store different row types in
+ the same table. The Contacts Provider's "data" table
+ {@link android.provider.ContactsContract.Data} is an example of a schema-independent
+ table.
+ </p>
+ </li>
+</ul>
+<!-- Designing Content URIs -->
+<h2 id="ContentURI">Designing Content URIs</h2>
+<p>
+ A <strong>content URI</strong> is a URI that identifies data in a provider. Content URIs include
+ the symbolic name of the entire provider (its <strong>authority</strong>) and a
+ name that points to a table or file (a <strong>path</strong>). The optional id part points to
+ an individual row in a table. Every data access method of
+ {@link android.content.ContentProvider} has a content URI as an argument; this allows you to
+ determine the table, row, or file to access.
+</p>
+<p>
+ The basics of content URIs are described in the topic
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics</a>.
+</p>
+<h3>Designing an authority</h3>
+<p>
+ A provider usually has a single authority, which serves as its Android-internal name. To
+ avoid conflicts with other providers, you should use Internet domain ownership (in reverse)
+ as the basis of your provider authority. Because this recommendation is also true for Android
+ package names, you can define your provider authority as an extension of the name
+ of the package containing the provider. For example, if your Android package name is
+ <code>com.example.<appname></code>, you should give your provider the
+ authority <code>com.example.<appname>.provider</code>.
+</p>
+<h3>Designing a path structure</h3>
+<p>
+ Developers usually create content URIs from the authority by appending paths that point to
+ individual tables. For example, if you have two tables <em>table1</em> and
+ <em>table2</em>, you combine the authority from the previous example to yield the
+ content URIs
+ <code>com.example.<appname>.provider/table1</code> and
+ <code>com.example.<appname>.provider/table2</code>. Paths aren't
+ limited to a single segment, and there doesn't have to be a table for each level of the path.
+</p>
+<h3>Handling content URI IDs</h3>
+<p>
+ By convention, providers offer access to a single row in a table by accepting a content URI
+ with an ID value for the row at the end of the URI. Also by convention, providers match the
+ ID value to the table's <code>_ID</code> column, and perform the requested access against the
+ row that matches.
+</p>
+<p>
+ This convention facilitates a common design pattern for apps accessing a provider. The app
+ does a query against the provider and displays the resulting {@link android.database.Cursor}
+ in a {@link android.widget.ListView} using a {@link android.widget.CursorAdapter}.
+ The definition of {@link android.widget.CursorAdapter} requires one of the columns in the
+ {@link android.database.Cursor} to be <code>_ID</code>
+</p>
+<p>
+ The user then picks one of the displayed rows from the UI in order to look at or modify the
+ data. The app gets the corresponding row from the {@link android.database.Cursor} backing the
+ {@link android.widget.ListView}, gets the <code>_ID</code> value for this row, appends it to
+ the content URI, and sends the access request to the provider. The provider can then do the
+ query or modification against the exact row the user picked.
+</p>
+<h3>Content URI patterns</h3>
+<p>
+ To help you choose which action to take for an incoming content URI, the provider API includes
+ the convenience class {@link android.content.UriMatcher}, which maps content URI "patterns" to
+ integer values. You can use the integer values in a <code>switch</code> statement that
+ chooses the desired action for the content URI or URIs that match a particular pattern.
+</p>
+<p>
+ A content URI pattern matches content URIs using wildcard characters:
+</p>
+ <ul>
+ <li>
+ <strong><code>*</code>:</strong> Matches a string of any valid characters of any length.
+ </li>
+ <li>
+ <strong><code>#</code>:</strong> Matches a string of numeric characters of any length.
+ </li>
+ </ul>
+<p>
+ As an example of designing and coding content URI handling, consider a provider with the
+ authority <code>com.example.app.provider</code> that recognizes the following content URIs
+ pointing to tables:
+</p>
+<ul>
+ <li>
+ <code>content://com.example.app.provider/table1</code>: A table called <code>table1</code>.
+ </li>
+ <li>
+ <code>content://com.example.app.provider/table2/dataset1</code>: A table called
+ <code>dataset1</code>.
+ </li>
+ <li>
+ <code>content://com.example.app.provider/table2/dataset2</code>: A table called
+ <code>dataset2</code>.
+ </li>
+ <li>
+ <code>content://com.example.app.provider/table3</code>: A table called <code>table3</code>.
+ </li>
+</ul>
+<p>
+ The provider also recognizes these content URIs if they have a row ID appended to them, as
+ for example <code>content://com.example.app.provider/table3/1</code> for the row identified by
+ <code>1</code> in <code>table3</code>.
+</p>
+<p>
+ The following content URI patterns would be possible:
+</p>
+<dl>
+ <dt>
+ <code>content://com.example.app.provider/*</code>
+ </dt>
+ <dd>
+ Matches any content URI in the provider.
+ </dd>
+ <dt>
+ <code>content://com.example.app.provider/table2/*</code>:
+ </dt>
+ <dd>
+ Matches a content URI for the tables <code>dataset1</code>
+ and <code>dataset2</code>, but doesn't match content URIs for <code>table1</code> or
+ <code>table3</code>.
+ </dd>
+ <dt>
+ <code>content://com.example.app.provider/table3/#</code>: Matches a content URI
+ for single rows in <code>table3</code>, such as
+ <code>content://com.example.app.provider/table3/6</code> for the row identified by
+ <code>6</code>.
+ </dt>
+</dl>
+<p>
+ The following code snippet shows how the methods in {@link android.content.UriMatcher} work.
+ This code handles URIs for an entire table differently from URIs for a
+ single row, by using the content URI pattern
+ <code>content://<authority>/<path></code> for tables, and
+ <code>content://<authority>/<path>/<id></code> for single rows.
+</p>
+<p>
+ The method {@link android.content.UriMatcher#addURI(String, String, int) addURI()} maps an
+ authority and path to an integer value. The method android.content.UriMatcher#match(Uri)
+ match()} returns the integer value for a URI. A <code>switch</code> statement
+ chooses between querying the entire table, and querying for a single record:
+</p>
+<pre class="prettyprint">
+public class ExampleProvider extends ContentProvider {
+...
+ // Creates a UriMatcher object.
+ private static final UriMatcher sUriMatcher;
+...
+ /*
+ * The calls to addURI() go here, for all of the content URI patterns that the provider
+ * should recognize. For this snippet, only the calls for table 3 are shown.
+ */
+...
+ /*
+ * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
+ * in the path
+ */
+ sUriMatcher.addURI("com.example.app.provider", "table3", 1);
+
+ /*
+ * Sets the code for a single row to 2. In this case, the "#" wildcard is
+ * used. "content://com.example.app.provider/table3/3" matches, but
+ * "content://com.example.app.provider/table3 doesn't.
+ */
+ sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);
+...
+ // Implements ContentProvider.query()
+ public Cursor query(
+ Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+...
+ /*
+ * Choose the table to query and a sort order based on the code returned for the incoming
+ * URI. Here, too, only the statements for table 3 are shown.
+ */
+ switch (sUriMatcher.match(uri)) {
+
+
+ // If the incoming URI was for all of table3
+ case 1:
+
+ if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
+ break;
+
+ // If the incoming URI was for a single row
+ case 2:
+
+ /*
+ * Because this URI was for a single row, the _ID value part is
+ * present. Get the last path segment from the URI; this is the _ID value.
+ * Then, append the value to the WHERE clause for the query
+ */
+ selection = selection + "_ID = " uri.getLastPathSegment();
+ break;
+
+ default:
+ ...
+ // If the URI is not recognized, you should do some error handling here.
+ }
+ // call the code to actually do the query
+ }
+</pre>
+<p>
+ Another class, {@link android.content.ContentUris}, provides convenience methods for working
+ with the <code>id</code> part of content URIs. The classes {@link android.net.Uri} and
+ {@link android.net.Uri.Builder} include convenience methods for parsing existing
+ {@link android.net.Uri} objects and building new ones.
+</p>
+
+<!-- Implementing the ContentProvider class -->
+<h2 id="ContentProvider">Implementing the ContentProvider Class</h2>
+<p>
+ The {@link android.content.ContentProvider} instance manages access
+ to a structured set of data by handling requests from other applications. All forms
+ of access eventually call {@link android.content.ContentResolver}, which then calls a concrete
+ method of {@link android.content.ContentProvider} to get access.
+</p>
+<h3 id="RequiredAccess">Required methods</h3>
+<p>
+ The abstract class {@link android.content.ContentProvider} defines six abstract methods that
+ you must implement as part of your own concrete subclass. All of these methods except
+ {@link android.content.ContentProvider#onCreate() onCreate()} are called by a client application
+ that is attempting to access your content provider:
+</p>
+<dl>
+ <dt>
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String)
+ query()}
+ </dt>
+ <dd>
+ Retrieve data from your provider. Use the arguments to select the table to
+ query, the rows and columns to return, and the sort order of the result.
+ Return the data as a {@link android.database.Cursor} object.
+ </dd>
+ <dt>
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}
+ </dt>
+ <dd>
+ Insert a new row into your provider. Use the arguments to select the
+ destination table and to get the column values to use. Return a content URI for the
+ newly-inserted row.
+ </dd>
+ <dt>
+ {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[])
+ update()}
+ </dt>
+ <dd>
+ Update existing rows in your provider. Use the arguments to select the table and rows
+ to update and to get the updated column values. Return the number of rows updated.
+ </dd>
+ <dt>
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()}
+ </dt>
+ <dd>
+ Delete rows from your provider. Use the arguments to select the table and the rows to
+ delete. Return the number of rows deleted.
+ </dd>
+ <dt>
+ {@link android.content.ContentProvider#getType(Uri) getType()}
+ </dt>
+ <dd>
+ Return the MIME type corresponding to a content URI. This method is described in more
+ detail in the section <a href="#MIMETypes">Implementing Content Provider MIME Types</a>.
+ </dd>
+ <dt>
+ {@link android.content.ContentProvider#onCreate() onCreate()}
+ </dt>
+ <dd>
+ Initialize your provider. The Android system calls this method immediately after it
+ creates your provider. Notice that your provider is not created until a
+ {@link android.content.ContentResolver} object tries to access it.
+ </dd>
+</dl>
+<p>
+ Notice that these methods have the same signature as the identically-named
+ {@link android.content.ContentResolver} methods.
+</p>
+<p>
+ Your implementation of these methods should account for the following:
+</p>
+<ul>
+ <li>
+ All of these methods except {@link android.content.ContentProvider#onCreate() onCreate()}
+ can be called by multiple threads at once, so they must be thread-safe. To learn
+ more about multiple threads, see the topic
+ <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">
+ Processes and Threads</a>.
+ </li>
+ <li>
+ Avoid doing lengthy operations in {@link android.content.ContentProvider#onCreate()
+ onCreate()}. Defer initialization tasks until they are actually needed.
+ The section <a href="#OnCreate">Implementing the onCreate() method</a>
+ discusses this in more detail.
+ </li>
+ <li>
+ Although you must implement these methods, your code does not have to do anything except
+ return the expected data type. For example, you may want to prevent other applications
+ from inserting data into some tables. To do this, you can ignore the call to
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} and return
+ 0.
+ </li>
+</ul>
+<h3 id="Query">Implementing the query() method</h3>
+<p>
+ The
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String)
+ ContentProvider.query()} method must return a {@link android.database.Cursor} object, or if it
+ fails, throw an {@link java.lang.Exception}. If you are using an SQLite database as your data
+ storage, you can simply return the {@link android.database.Cursor} returned by one of the
+ <code>query()</code> methods of the {@link android.database.sqlite.SQLiteDatabase} class.
+ If the query does not match any rows, you should return a {@link android.database.Cursor}
+ instance whose {@link android.database.Cursor#getCount()} method returns 0.
+ You should return <code>null</code> only if an internal error occurred during the query process.
+</p>
+<p>
+ If you aren't using an SQLite database as your data storage, use one of the concrete subclasses
+ of {@link android.database.Cursor}. For example, the {@link android.database.MatrixCursor} class
+ implements a cursor in which each row is an array of {@link java.lang.Object}. With this class,
+ use {@link android.database.MatrixCursor#addRow(Object[]) addRow()} to add a new row.
+</p>
+<p>
+ Remember that the Android system must be able to communicate the {@link java.lang.Exception}
+ across process boundaries. Android can do this for the following exceptions that may be useful
+ in handling query errors:
+</p>
+<ul>
+ <li>
+ {@link java.lang.IllegalArgumentException} (You may choose to throw this if your provider
+ receives an invalid content URI)
+ </li>
+ <li>
+ {@link java.lang.NullPointerException}
+ </li>
+</ul>
+<h3 id="Insert">Implementing the insert() method</h3>
+<p>
+ The {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} method adds a
+ new row to the appropriate table, using the values in the {@link android.content.ContentValues}
+ argument. If a column name is not in the {@link android.content.ContentValues} argument, you
+ may want to provide a default value for it either in your provider code or in your database
+ schema.
+</p>
+<p>
+ This method should return the content URI for the new row. To construct this, append the new
+ row's <code>_ID</code> (or other primary key) value to the table's content URI, using
+ {@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}.
+</p>
+<h3 id="Delete">Implementing the delete() method</h3>
+<p>
+ The {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} method
+ does not have to physically delete rows from your data storage. If you are using a sync adapter
+ with your provider, you should consider marking a deleted row
+ with a "delete" flag rather than removing the row entirely. The sync adapter can
+ check for deleted rows and remove them from the server before deleting them from the provider.
+</p>
+<h3 id="Update">Implementing the update() method</h3>
+<p>
+ The {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[])
+ update()} method takes the same {@link android.content.ContentValues} argument used by
+ {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}, and the
+ same <code>selection</code> and <code>selectionArgs</code> arguments used by
+ {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} and
+ {@link android.content.ContentProvider#query(Uri, String[], String, String[], String)
+ ContentProvider.query()}. This may allow you to re-use code between these methods.
+</p>
+<h3 id="OnCreate">Implementing the onCreate() method</h3>
+<p>
+ The Android system calls {@link android.content.ContentProvider#onCreate()
+ onCreate()} when it starts up the provider. You should perform only fast-running initialization
+ tasks in this method, and defer database creation and data loading until the provider actually
+ receives a request for the data. If you do lengthy tasks in
+ {@link android.content.ContentProvider#onCreate() onCreate()}, you will slow down your
+ provider's startup. In turn, this will slow down the response from the provider to other
+ applications.
+</p>
+<p>
+ For example, if you are using an SQLite database you can create
+ a new {@link android.database.sqlite.SQLiteOpenHelper} object in
+ {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()},
+ and then create the SQL tables the first time you open the database. To facilitate this, the
+ first time you call {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase
+ getWritableDatabase()}, it automatically calls the
+ {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase)
+ SQLiteOpenHelper.onCreate()} method.
+</p>
+<p>
+ The following two snippets demonstrate the interaction between
+ {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} and
+ {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase)
+ SQLiteOpenHelper.onCreate()}. The first snippet is the implementation of
+ {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}:
+</p>
+<pre class="prettyprint">
+public class ExampleProvider extends ContentProvider
+
+ /*
+ * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
+ * in a following snippet.
+ */
+ private MainDatabaseHelper mOpenHelper;
+
+ // Defines the database name
+ private static final String DBNAME = "mydb";
+
+ // Holds the database object
+ private SQLiteDatabase db;
+
+ public boolean onCreate() {
+
+ /*
+ * Creates a new helper object. This method always returns quickly.
+ * Notice that the database itself isn't created or opened
+ * until SQLiteOpenHelper.getWritableDatabase is called
+ */
+ mOpenHelper = new SQLiteOpenHelper(
+ getContext(), // the application context
+ DBNAME, // the name of the database)
+ null, // uses the default SQLite cursor
+ 1 // the version number
+ );
+
+ return true;
+ }
+
+ ...
+
+ // Implements the provider's insert method
+ public Cursor insert(Uri uri, ContentValues values) {
+ // Insert code here to determine which table to open, handle error-checking, and so forth
+
+ ...
+
+ /*
+ * Gets a writeable database. This will trigger its creation if it doesn't already exist.
+ *
+ */
+ db = mOpenHelper.getWritableDatabase();
+ }
+}
+</pre>
+<p>
+ The next snippet is the implementation of
+ {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase)
+ SQLiteOpenHelper.onCreate()}, including a helper class:
+</p>
+<pre class="prettyprint">
+...
+// A string that defines the SQL statement for creating a table
+private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
+ "main " + // Table's name
+ "(" + // The columns in the table
+ " _ID INTEGER PRIMARY KEY, " +
+ " WORD TEXT"
+ " FREQUENCY INTEGER " +
+ " LOCALE TEXT )";
+...
+/**
+ * Helper class that actually creates and manages the provider's underlying data repository.
+ */
+protected static final class MainDatabaseHelper extends SQLiteOpenHelper {
+
+ /*
+ * Instantiates an open helper for the provider's SQLite data repository
+ * Do not do database creation and upgrade here.
+ */
+ MainDatabaseHelper(Context context) {
+ super(context, DBNAME, null, 1);
+ }
+
+ /*
+ * Creates the data repository. This is called when the provider attempts to open the
+ * repository and SQLite reports that it doesn't exist.
+ */
+ public void onCreate(SQLiteDatabase db) {
+
+ // Creates the main table
+ db.execSQL(SQL_CREATE_MAIN);
+ }
+}
+</pre>
+
+
+<!-- Implementing ContentProvider MIME Types -->
+<h2 id="MIMETypes">Implementing ContentProvider MIME Types</h2>
+<p>
+ The {@link android.content.ContentProvider} class has two methods for returning MIME types:
+</p>
+<dl>
+ <dt>
+ {@link android.content.ContentProvider#getType(Uri) getType()}
+ </dt>
+ <dd>
+ One of the required methods that you must implement for any provider.
+ </dd>
+ <dt>
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}
+ </dt>
+ <dd>
+ A method that you're expected to implement if your provider offers files.
+ </dd>
+</dl>
+<h3 id="TableMIMETypes">MIME types for tables</h3>
+<p>
+ The {@link android.content.ContentProvider#getType(Uri) getType()} method returns a
+ {@link java.lang.String} in MIME format that describes the type of data returned by the content
+ URI argument. The {@link android.net.Uri} argument can be a pattern rather than a specific URI;
+ in this case, you should return the type of data associated with content URIs that match the
+ pattern.
+</p>
+<p>
+ For common types of data such as as text, HTML, or JPEG,
+ {@link android.content.ContentProvider#getType(Uri) getType()} should return the standard
+ MIME type for that data. A full list of these standard types is available on the
+ <a href="http://www.iana.org/assignments/media-types/index.htm">IANA MIME Media Types</a>
+ website.
+</p>
+<p>
+ For content URIs that point to a row or rows of table data,
+ {@link android.content.ContentProvider#getType(Uri) getType()} should return
+ a MIME type in Android's vendor-specific MIME format:
+</p>
+<ul>
+ <li>
+ Type part: <code>vnd</code>
+ </li>
+ <li>
+ Subtype part:
+ <ul>
+ <li>
+ If the URI pattern is for a single row: <code>android.cursor.<strong>item</strong>/</code>
+ </li>
+ <li>
+ If the URI pattern is for more than one row: <code>android.cursor.<strong>dir</strong>/</code>
+ </li>
+ </ul>
+ </li>
+ <li>
+ Provider-specific part: <code>vnd.<name></code>.<code><type></code>
+ <p>
+ You supply the <code><name></code> and <code><type></code>.
+ The <code><name></code> value should be globally unique,
+ and the <code><type></code> value should be unique to the corresponding URI
+ pattern. A good choice for <code><name></code> is your company's name or
+ some part of your application's Android package name. A good choice for the
+ <code><type></code> is a string that identifies the table associated with the
+ URI.
+ </p>
+
+ </li>
+</ul>
+<p>
+ For example, if a provider's authority is
+ <code>com.example.app.provider</code>, and it exposes a table named
+ <code>table1</code>, the MIME type for multiple rows in <code>table1</code> is:
+</p>
+<pre>
+vnd.android.cursor.<strong>dir</strong>/vnd.com.example.provider.table1
+</pre>
+<p>
+ For a single row of <code>table1</code>, the MIME type is:
+</p>
+<pre>
+vnd.android.cursor.<strong>item</strong>/vnd.com.example.provider.table1
+</pre>
+<h3 id="FileMIMETypes">MIME types for files</h3>
+<p>
+ If your provider offers files, implement
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}.
+ The method returns a {@link java.lang.String} array of MIME types for the files your provider
+ can return for a given content URI. You should filter the MIME types you offer by the MIME type
+ filter argument, so that you return only those MIME types that the client wants to handle.
+</p>
+<p>
+ For example, consider a provider that offers photo images as files in <code>.jpg</code>,
+ <code>.png</code>, and <code>.gif</code> format.
+ If an application calls {@link android.content.ContentResolver#getStreamTypes(Uri, String)
+ ContentResolver.getStreamTypes()} with the filter string <code>image/*</code> (something that
+ is an "image"),
+ then the {@link android.content.ContentProvider#getStreamTypes(Uri, String)
+ ContentProvider.getStreamTypes()} method should return the array:
+</p>
+<pre>
+{ "image/jpeg", "image/png", "image/gif"}
+</pre>
+<p>
+ If the app is only interested in <code>.jpg</code> files, then it can call
+ {@link android.content.ContentResolver#getStreamTypes(Uri, String)
+ ContentResolver.getStreamTypes()} with the filter string <code>*\/jpeg</code>, and
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String)
+ ContentProvider.getStreamTypes()} should return:
+<pre>
+{"image/jpeg"}
+</pre>
+<p>
+ If your provider doesn't offer any of the MIME types requested in the filter string,
+ {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}
+ should return <code>null</code>.
+</p>
+
+
+<!-- Implementing a Contract Class -->
+<h2 id="ContractClass">Implementing a Contract Class</h2>
+<p>
+ A contract class is a <code>public final</code> class that contains constant definitions for the
+ URIs, column names, MIME types, and other meta-data that pertain to the provider. The class
+ establishes a contract between the provider and other applications by ensuring that the provider
+ can be correctly accessed even if there are changes to the actual values of URIs, column names,
+ and so forth.
+</p>
+<p>
+ A contract class also helps developers because it usually has mnemonic names for its constants,
+ so developers are less likely to use incorrect values for column names or URIs. Since it's a
+ class, it can contain Javadoc documentation. Integrated development environments such as
+ Eclipse can auto-complete constant names from the contract class and display Javadoc for the
+ constants.
+</p>
+<p>
+ Developers can't access the contract class's class file from your application, but they can
+ statically compile it into their application from a <code>.jar</code> file you provide.
+</p>
+<p>
+ The {@link android.provider.ContactsContract} class and its nested classes are examples of
+ contract classes.
+</p>
+<h2 id="Permissions">Implementing Content Provider Permissions</h2>
+<p>
+ Permissions and access for all aspects of the Android system are described in detail in the
+ topic <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>.
+ The topic <a href="{@docRoot}guide/topics/data/data-storage.html">Data Storage</a> also
+ described the security and permissions in effect for various types of storage.
+ In brief, the important points are:
+</p>
+<ul>
+ <li>
+ By default, data files stored on the device's internal storage are private to your
+ application and provider.
+ </li>
+ <li>
+ {@link android.database.sqlite.SQLiteDatabase} databases you create are private to your
+ application and provider.
+ </li>
+ <li>
+ By default, data files that you save to external storage are <em>public</em> and
+ <em>world-readable</em>. You can't use a content provider to restrict access to files in
+ external storage, because other applications can use other API calls to read and write them.
+ </li>
+ <li>
+ The method calls for opening or creating files or SQLite databases on your device's internal
+ storage can potentially give both read and write access to all other applications. If you
+ use an internal file or database as your provider's repository, and you give it
+ "world-readable" or "world-writeable" access, the permissions you set for your provider in
+ its manifest won't protect your data. The default access for files and databases in
+ internal storage is "private", and for your provider's repository you shouldn't change this.
+ </li>
+</ul>
+<p>
+ If you want to use content provider permissions to control access to your data, then you should
+ store your data in internal files, SQLite databases, or the "cloud" (for example,
+ on a remote server), and you should keep files and databases private to your application.
+</p>
+<h3>Implementing permissions</h3>
+<p>
+ All applications can read from or write to your provider, even if the underlying data is
+ private, because by default your provider does not have permissions set. To change this,
+ set permissions for your provider in your manifest file, using attributes or child
+ elements of the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element. You can set permissions that apply to the entire provider,
+ or to certain tables, or even to certain records, or all three.
+</p>
+<p>
+ You define permissions for your provider with one or more
+ <code><a href="{@docRoot}guide/topics/manifest/permission-element.html">
+ <permission></a></code> elements in your manifest file. To make the
+ permission unique to your provider, use Java-style scoping for the
+ <code><a href="{@docRoot}guide/topics/manifest/permission-element.html#nm">
+ android:name</a></code> attribute. For example, name the read permission
+ <code>com.example.app.provider.permission.READ_PROVIDER</code>.
+
+</p>
+<p>
+ The following list describes the scope of provider permissions, starting with the
+ permissions that apply to the entire provider and then becoming more fine-grained.
+ More fine-grained permissions take precedence over ones with larger scope:
+</p>
+<dl>
+ <dt>
+ Single read-write provider-level permission
+ </dt>
+ <dd>
+ One permission that controls both read and write access to the entire provider, specified
+ with the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn">
+ android:permission</a></code> attribute of the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element.
+ </dd>
+ <dt>
+ Separate read and write provider-level permission
+ </dt>
+ <dd>
+ A read permission and a write permission for the entire provider. You specify them
+ with the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#rprmsn">
+ android:readPermission</a></code> and
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#wprmsn">
+ android:writePermission</a></code> attributes of the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element. They take precedence over the permission required by
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn">
+ android:permission</a></code>.
+ </dd>
+ <dt>
+ Path-level permission
+ </dt>
+ <dd>
+ Read, write, or read/write permission for a content URI in your provider. You specify
+ each URI you want to control with a
+ <code><a href="{@docRoot}guide/topics/manifest/path-permission-element.html">
+ <path-permission></a></code> child element of the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element. For each content URI you specify, you can specify a
+ read/write permission, a read permission, or a write permission, or all three. The read and
+ write permissions take precedence over the read/write permission. Also, path-level
+ permission takes precedence over provider-level permissions.
+ </dd>
+ <dt>
+ Temporary permission
+ </dt>
+ <dd>
+ A permission level that grants temporary access to an application, even if the application
+ doesn't have the permissions that are normally required. The temporary
+ access feature reduces the number of permissions an application has to request in
+ its manifest. When you turn on temporary permissions, the only applications that need
+ "permanent" permissions for your provider are ones that continually access all
+ your data.
+ <p>
+ Consider the permissions you need to implement an email provider and app, when you
+ want to allow an outside image viewer application to display photo attachments from your
+ provider. To give the image viewer the necessary access without requiring permissions,
+ set up temporary permissions for content URIs for photos. Design your email app so
+ that when the user wants to display a photo, the app sends an intent containing the
+ photo's content URI and permission flags to the image viewer. The image viewer can
+ then query your email provider to retrieve the photo, even though the viewer doesn't
+ have the normal read permission for your provider.
+ </p>
+ <p>
+ To turn on temporary permissions, either set the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn">
+ android:grantUriPermissions</a></code> attribute of the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element, or add one or more
+ <code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html">
+ <grant-uri-permission></a></code> child elements to your
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element. If you use temporary permissions, you have to call
+ {@link android.content.Context#revokeUriPermission(Uri, int)
+ Context.revokeUriPermission()} whenever you remove support for a content URI from your
+ provider, and the content URI is associated with a temporary permission.
+ </p>
+ <p>
+ The attribute's value determines how much of your provider is made accessible.
+ If the attribute is set to <code>true</code>, then the system will grant temporary
+ permission to your entire provider, overriding any other permissions that are required
+ by your provider-level or path-level permissions.
+ </p>
+ <p>
+ If this flag is set to <code>false</code>, then you must add
+ <code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html">
+ <grant-uri-permission></a></code> child elements to your
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element. Each child element specifies the content URI or
+ URIs for which temporary access is granted.
+ </p>
+ <p>
+ To delegate temporary access to an application, an intent must contain
+ the {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} or the
+ {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} flags, or both. These
+ are set with the {@link android.content.Intent#setFlags(int) setFlags()} method.
+ </p>
+ <p>
+ If the <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn">
+ android:grantUriPermissions</a></code> attribute is not present, it's assumed to be
+ <code>false</code>.
+ </p>
+ </dd>
+</dl>
+
+
+
+<!-- The Provider Element -->
+<h2 id="ProviderElement">The <provider> Element</h2>
+<p>
+ Like {@link android.app.Activity} and {@link android.app.Service} components,
+ a subclass of {@link android.content.ContentProvider}
+ must be defined in the manifest file for its application, using the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element. The Android system gets the following information from
+ the element:
+<dl>
+ <dt>
+ Authority
+ (<a href="{@docRoot}guide/topics/manifest/provider-element.html#auth">{@code
+ android:authorities}</a>)
+ </dt>
+ <dd>
+ Symbolic names that identify the entire provider within the system. This
+ attribute is described in more detail in the section
+ <a href="#ContentURI">Designing Content URIs</a>.
+ </dd>
+ <dt>
+ Provider class name
+ (<code>
+<a href="{@docRoot}guide/topics/manifest/provider-element.html#nm">android:name</a>
+ </code>)
+ </dt>
+ <dd>
+ The class that implements {@link android.content.ContentProvider}. This class is
+ described in more detail in the section
+ <a href="#ContentProvider">Implementing the ContentProvider Class</a>.
+ </dd>
+ <dt>
+ Permissions
+ </dt>
+ <dd>
+ Attributes that specify the permissions that other applications must have in order to access
+ the provider's data:
+ <ul>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn">
+ android:grantUriPermssions</a></code>: Temporary permission flag.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn">
+ android:permission</a></code>: Single provider-wide read/write permission.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#rprmsn">
+ android:readPermission</a></code>: Provider-wide read permission.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#wprmsn">
+ android:writePermission</a></code>: Provider-wide write permission.
+ </li>
+ </ul>
+ <p>
+ Permissions and their corresponding attributes are described in more
+ detail in the section
+ <a href="#Permissions">Implementing Content Provider Permissions</a>.
+ </p>
+ </dd>
+ <dt>
+ Startup and control attributes
+ </dt>
+ <dd>
+ These attributes determine how and when the Android system starts the provider, the
+ process characteristics of the provider, and other run-time settings:
+ <ul>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#enabled">
+ android:enabled</a></code>: Flag allowing the system to start the provider.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#exported">
+ android:exported</a></code>: Flag allowing other applications to use this provider.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#init">
+ android:initOrder</a></code>: The order in which this provider should be started,
+ relative to other providers in the same process.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#multi">
+ android:multiProcess</a></code>: Flag allowing the system to start the provider
+ in the same process as the calling client.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#proc">
+ android:process</a></code>: The name of the process in which the provider should
+ run.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#sync">
+ android:syncable</a></code>: Flag indicating that the provider's data is to be
+ sync'ed with data on a server.
+ </li>
+ </ul>
+ <p>
+ The attributes are fully documented in the dev guide topic for the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code>
+ element.
+ </p>
+ </dd>
+ <dt>
+ Informational attributes
+ </dt>
+ <dd>
+ An optional icon and label for the provider:
+ <ul>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#icon">
+ android:icon</a></code>: A drawable resource containing an icon for the provider.
+ The icon appears next to the provider's label in the list of apps in
+ <em>Settings</em> > <em>Apps</em> > <em>All</em>.
+ </li>
+ <li>
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#label">
+ android:label</a></code>: An informational label describing the provider or its
+ data, or both. The label appears in the list of apps in
+ <em>Settings</em> > <em>Apps</em> > <em>All</em>.
+ </li>
+ </ul>
+ <p>
+ The attributes are fully documented in the dev guide topic for the
+ <code><a href="{@docRoot}guide/topics/manifest/provider-element.html">
+ <provider></a></code> element.
+ </p>
+ </dd>
+</dl>
+
+<!-- Intent Access -->
+<h2 id="Intents">Intents and Data Access</h2>
+<p>
+ Applications can access a content provider indirectly with an {@link android.content.Intent}.
+ The application does not call any of the methods of {@link android.content.ContentResolver} or
+ {@link android.content.ContentProvider}. Instead, it sends an intent that starts an activity,
+ which is often part of the provider's own application. The destination activity is in charge of
+ retrieving and displaying the data in its UI. Depending on the action in the intent, the
+ destination activity may also prompt the user to make modifications to the provider's data.
+ An intent may also contain "extras" data that the destination activity displays
+ in the UI; the user then has the option of changing this data before using it to modify the
+ data in the provider.
+</p>
+<p>
+
+</p>
+<p>
+ You may want to use intent access to help ensure data integrity. Your provider may depend
+ on having data inserted, updated, and deleted according to strictly defined business logic. If
+ this is the case, allowing other applications to directly modify your data may lead to
+ invalid data. If you want developers to use intent access, be sure to document it thoroughly.
+ Explain to them why intent access using your own application's UI is better than trying to
+ modify the data with their code.
+</p>
+<p>
+ Handling an incoming intent that wishes to modify your provider's data is no different from
+ handling other intents. You can learn more about using intents by reading the topic
+ <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>.
+</p>
diff --git a/docs/html/guide/topics/providers/content-providers.jd b/docs/html/guide/topics/providers/content-providers.jd
index 95331ce..1707f03 100644
--- a/docs/html/guide/topics/providers/content-providers.jd
+++ b/docs/html/guide/topics/providers/content-providers.jd
@@ -1,922 +1,96 @@
page.title=Content Providers
@jd:body
-
<div id="qv-wrapper">
<div id="qv">
-<h2>In this document</h2>
+
+<!-- In this document -->
+<h2>Topics</h2>
<ol>
-<li><a href="#basics">Content provider basics</a></li>
-<li><a href="#querying">Querying a content provider</a></li>
-<li><a href="#modifying">Modifying data in a provider</a></li>
-<li><a href="#creating">Creating a content provider</a></li>
-<li><a href="#urisum">Content URI summary</a></li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/content-provider-creating.html">
+ Creating a Content Provider</a>
+ </li>
+ <li>
+ <a href="{@docRoot}guide/topics/providers/calendar-provider.html">Calendar Provider</a>
+ </li>
</ol>
-<h2>Key classes</h2>
-<ol>
-<li>{@link android.content.ContentProvider}</li>
-<li>{@link android.content.ContentResolver}</li>
-<li>{@link android.database.Cursor}</li>
-</ol>
-
-<h2>See also</h2>
-<ol>
- <li><a href="{@docRoot}guide/topics/providers/calendar-provider.html">Calendar Provider</a></li>
-</ol>
+ <!-- Related Samples -->
+<h2>Related Samples</h2>
+ <ol>
+ <li>
+ <a href="{@docRoot}resources/samples/ContactManager/index.html">
+ Contact Manager</a> application
+ </li>
+ <li>
+ <a
+ href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html">
+ "Cursor (People)"
+ </a>
+ </li>
+ <li>
+ <a
+ href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html">
+ "Cursor (Phones)"</a>
+ </li>
+ </ol>
</div>
</div>
-
<p>
-Content providers store and retrieve data and make it accessible to all
-applications. They're the only way to share data across applications; there's
-no common storage area that all Android packages can access.
-</p>
-
-<p>
-Android ships with a number of content providers for common data types
-(audio, video, images, personal contact information, and so on). You can
-see some of them listed in the {@link android.provider android.provider}
-package. You can query these providers for the data they contain (although,
-for some, you must acquire the proper permission to read the data).
-</p>
-
-<p class="note"><strong>Note:</strong> Android 4.0 introduces the Calendar
-Provider. For more information, see <a
-href="{@docRoot}guide/topics/providers/calendar-provider.html">Calendar
-Provider</a>.</p>
-<p>
-If you want to make your own data public, you have two options: You can
-create your own content provider (a {@link android.content.ContentProvider}
-subclass) or you can add the data to an existing provider — if there's
-one that controls the same type of data and you have permission to write to it.
-</p>
-
-<p>
-This document is an introduction to using content providers. After a
-brief discussion of the fundamentals, it explores how to query a content
-provider, how to modify data controlled by a provider, and how to create
-a content provider of your own.
-</p>
-
-
-<h2><a name="basics"></a>Content Provider Basics</h2>
-
-<p>
-How a content provider actually stores its data under the covers is
-up to its designer. But all content providers implement a common interface
-for querying the provider and returning results — as well as for
-adding, altering, and deleting data.
-</p>
-
-<p>
-It's an interface that clients use indirectly, most generally through
-{@link android.content.ContentResolver} objects. You get a ContentResolver
-by calling <code>{@link android.content.Context#getContentResolver
-getContentResolver()}</code> from within the implementation of an Activity
-or other application component:
-</p>
-
-<pre>ContentResolver cr = getContentResolver();</pre>
-
-<p>
-You can then use the ContentResolver's methods to interact with whatever
-content providers you're interested in.
-</p>
-
-<p>
-When a query is initiated, the Android system identifies the content provider
-that's the target of the query and makes sure that it is up and running.
-The system instantiates all ContentProvider objects; you never need to do it
-on your own. In fact, you never deal directly with ContentProvider objects
-at all. Typically, there's just a single instance of each type of
-ContentProvider. But it can communicate with multiple ContentResolver objects
-in different applications and processes. The interaction between processes is
-handled by the ContentResolver and ContentProvider classes.
-</p>
-
-
-<h3>The data model</h3>
-
-<p>
-Content providers expose their data as a simple table on a database model,
-where each row is a record and each column is data of a particular type
-and meaning. For example, information about people and their phone numbers
-might be exposed as follows:
-</p>
-
-<table>
- <tr>
- <th scope="col">_ID</th>
- <th scope="col">NUMBER</th>
- <th scope="col">NUMBER_KEY</th>
- <th scope="col">LABEL</th>
- <th scope="col">NAME</th>
- <th scope="col">TYPE</th>
- </tr>
- <tr>
- <td>13</td>
- <td>(425) 555 6677</td>
- <td>425 555 6677</td>
- <td>Kirkland office</td>
- <td>Bully Pulpit</td>
- <td>{@code TYPE_WORK}</td>
- </tr>
- <tr>
- <td>44</td>
- <td>(212) 555-1234</td>
- <td>212 555 1234</td>
- <td>NY apartment</td>
- <td>Alan Vain</td>
- <td>{@code TYPE_HOME}</td>
- </tr>
- <tr>
- <td>45</td>
- <td>(212) 555-6657</td>
- <td>212 555 6657</td>
- <td>Downtown office</td>
- <td>Alan Vain</td>
- <td>{@code TYPE_MOBILE}</td>
- </tr>
- <tr>
- <td>53</td>
- <td>201.555.4433</td>
- <td>201 555 4433</td>
- <td>Love Nest</td>
- <td>Rex Cars</td>
- <td>{@code TYPE_HOME}</td>
- </tr>
-</table>
-
-<p>
-Every record includes a numeric {@code _ID} field that uniquely identifies
-the record within the table. IDs can be used to match records in related
-tables — for example, to find a person's phone number in one table
-and pictures of that person in another.
-</p>
-
-<p>
-A query returns a {@link android.database.Cursor} object that can move from
-record to record and column to column to read the contents of each field.
-It has specialized methods for reading each type of data. So, to read a field,
-you must know what type of data the field contains. (There's more on query
-results and Cursor objects later.)
-</p>
-
-
-<h3><a name="uri"></a>URIs</h3>
-
-<p>
-Each content provider exposes a public URI (wrapped as a {@link android.net.Uri}
-object) that uniquely identifies its data set. A content provider that controls
-multiple data sets (multiple tables) exposes a separate URI for each one. All
-URIs for providers begin with the string "{@code content://}". The {@code content:}
-scheme identifies the data as being controlled by a content provider.
-</p>
-
-<p>
-If you're defining a content provider, it's a good idea to also define a
-constant for its URI, to simplify client code and make future updates cleaner.
-Android defines {@code CONTENT_URI} constants for all the providers that come
-with the platform. For example, the URI for the table that matches
-phone numbers to people and the URI for the table that holds pictures of
-people (both controlled by the Contacts content provider) are:
-</p>
-
-<p>
-<p style="margin-left: 2em">{@code android.provider.Contacts.Phones.CONTENT_URI}
-<br/>{@code android.provider.Contacts.Photos.CONTENT_URI}
+ Content providers manage access to a structured set of data. They encapsulate the
+ data, and provide mechanisms for defining data security. Content providers are the standard
+ interface that connects data in one process with code running in another process.
</p>
-
<p>
-The URI constant is used in all interactions with the content provider.
-Every {@link android.content.ContentResolver} method takes the URI
-as its first argument. It's what identifies which provider the ContentResolver
-should talk to and which table of the provider is being targeted.
-</p>
-
-
-<h2><a name="querying"></a>Querying a Content Provider</h2>
-
-<p>
-You need three pieces of information to query a content provider:
-</p>
-
-<ul>
-<li>The URI that identifies the provider</li>
-<li>The names of the data fields you want to receive</li>
-<li>The data types for those fields</li>
-</ul>
-
-<p>
-If you're querying a particular record, you also need the ID for that record.
-</p>
-
-
-<h3>Making the query</h3>
-
-<p>
-To query a content provider, you can use either the
-<code>{@link android.content.ContentResolver#query ContentResolver.query()}</code>
-method or the <code>{@link android.app.Activity#managedQuery
-Activity.managedQuery()}</code> method.
-Both methods take the same set of arguments, and both return a
-Cursor object. However, {@code managedQuery()}
-causes the activity to manage the life cycle of the Cursor. A managed Cursor
-handles all of the niceties, such as unloading itself when the activity pauses,
-and requerying itself when the activity restarts. You can ask an Activity to
-begin managing an unmanaged Cursor object for you by calling
-<code>{@link android.app.Activity#startManagingCursor
-Activity.startManagingCursor()}</code>.
-</p>
-
-<p>
-The first argument to either <code>{@link android.content.ContentResolver#query query()}</code>
-or <code>{@link android.app.Activity#managedQuery managedQuery()}</code> is the provider URI
-— the {@code CONTENT_URI} constant that identifies a particular
-ContentProvider and data set (see <a href="#uri">URIs</a> earlier).
-</p>
-
-<p>
-To restrict a query to just one record, you can append the {@code _ID} value for
-that record to the URI — that is, place a string matching the ID as the
-last segment of the path part of the URI. For example, if the ID is 23,
-the URI would be:
-</p>
-
-<p style="margin-left: 2em">{@code content://. . . ./23}</p>
-
-<p>
-There are some helper methods, particularly
-<code>{@link android.content.ContentUris#withAppendedId
-ContentUris.withAppendedId()}</code> and <code>{@link
-android.net.Uri#withAppendedPath Uri.withAppendedPath()}</code>,
-that make it easy to append an ID to a URI. Both are static methods that return
-a Uri object with the ID added. So, for example, if you were looking for record
-23 in the database of people contacts, you might construct a query as follows:
-</p>
-
-<pre>
-import android.provider.Contacts.People;
-import android.content.ContentUris;
-import android.net.Uri;
-import android.database.Cursor;
-
-// Use the ContentUris method to produce the base URI for the contact with _ID == 23.
-Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
-
-// Alternatively, use the Uri method to produce the base URI.
-// It takes a string rather than an integer.
-Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");
-
-// Then query for this specific record:
-Cursor cur = managedQuery(myPerson, null, null, null, null);
-</pre>
-
-<p>
-The other arguments to the <code>{@link android.content.ContentResolver#query query()}</code>
-and <code>{@link android.app.Activity#managedQuery managedQuery()}</code> methods delimit
-the query in more detail. They are:
-</p>
-
-<ul>
-<li>The names of the data columns that should be returned. A {@code null}
-value returns all columns. Otherwise, only columns that are listed by name
-are returned. All the content providers that come with the platform define
-constants for their columns. For example, the
-{@link android.provider.Contacts.Phones android.provider.Contacts.Phones} class
-defines constants for the names of the columns in the phone table illustrated
-earlier — {@code _ID}, {@code NUMBER}, {@code NUMBER_KEY}, {@code NAME},
-and so on.</li>
-
-<li><p>A filter detailing which rows to return, formatted as an SQL {@code WHERE}
-clause (excluding the {@code WHERE} itself). A {@code null} value returns
-all rows (unless the URI limits the query to a single record).</p></li>
-
-<li><p>Selection arguments.</p></li>
-
-<li><p>A sorting order for the rows that are returned, formatted as an SQL
-{@code ORDER BY} clause (excluding the {@code ORDER BY} itself). A {@code null}
-value returns the records in the default order for the table, which may be
-unordered.</p></li>
-</ul>
-
-<p>
-Let's look at an example query to retrieve a list of contact names and their
-primary phone numbers:
+ When you want to access data in a content provider, you use the
+ {@link android.content.ContentResolver} object in your
+ application's {@link android.content.Context} to communicate with the provider as a client.
+ The {@link android.content.ContentResolver} object communicates with the provider object, an
+ instance of a class that implements {@link android.content.ContentProvider}. The provider
+ object receives data requests from clients, performs the requested action, and
+ returns the results.
</p>
-
-<pre>
-import android.provider.Contacts.People;
-import android.database.Cursor;
-
-// Form an array specifying which columns to return.
-String[] projection = new String[] {
- People._ID,
- People._COUNT,
- People.NAME,
- People.NUMBER
- };
-
-// Get the base URI for the People table in the Contacts content provider.
-Uri contacts = People.CONTENT_URI;
-
-// Make the query.
-Cursor managedCursor = managedQuery(contacts,
- projection, // Which columns to return
- null, // Which rows to return (all rows)
- null, // Selection arguments (none)
- // Put the results in ascending order by name
- People.NAME + " ASC");
-</pre>
-
<p>
-This query retrieves data from the People table of the Contacts content
-provider. It gets the name, primary phone number, and unique record ID for
-each contact. It also reports the number of records that are returned as
-the {@code _COUNT} field of each record.
+ You don't need to develop your own provider if you don't intend to share your data with
+ other applications. However, you do need your own provider to provide custom search
+ suggestions in your own application. You also need your own provider if you want to copy and
+ paste complex data or files from your application to other applications.
</p>
-
<p>
-The constants for the names of the columns are defined in various interfaces
-— {@code _ID} and {@code _COUNT} in
-{@link android.provider.BaseColumns BaseColumns}, {@code NAME} in {@link android.provider.Contacts.PeopleColumns PeopleColumns}, and {@code NUMBER}
-in {@link android.provider.Contacts.PhonesColumns PhoneColumns}. The
-{@link android.provider.Contacts.People Contacts.People} class implements
-each of these interfaces, which is why the code example above could refer
-to them using just the class name.
+ Android itself includes content providers that manage data such as audio, video, images, and
+ personal contact information. You can see some of them listed in the reference
+ documentation for the
+ <code><a href="{@docRoot}reference/android/provider/package-summary.html">android.provider</a>
+ </code> package. With some restrictions, these providers are accessible to any Android
+ application.
+</p><p>
+ The following topics describe content providers in more detail:
</p>
-
-
-<h3>What a query returns</h3>
-
-<p>
-A query returns a set of zero or more database records. The names of the
-columns, their default order, and their data types are specific to each
-content provider.
-But every provider has an {@code _ID} column, which holds a unique numeric
-ID for each record. Every provider can also report the number
-of records returned as the {@code _COUNT} column; its value
-is the same for all rows.
-</p>
-
-<p>
-Here is an example result set for the query in the previous section:
-</p>
-
-<table border="1">
- <tbody>
- <tr>
- <th scope="col">_ID</th>
- <th scope="col">_COUNT</th>
- <th scope="col">NAME</th>
- <th scope="col">NUMBER</th>
- </tr>
- <tr>
- <td>44</td>
- <td>3</td>
- <td>Alan Vain</td>
- <td>212 555 1234</td>
- </tr>
- <tr>
- <td>13</td>
- <td>3</td>
- <td>Bully Pulpit</td>
- <td>425 555 6677</td>
- </tr>
- <tr>
- <td>53</td>
- <td>3</td>
- <td>Rex Cars</td>
- <td>201 555 4433</td>
- </tr>
- </tbody>
-</table>
-
-<p>
-The retrieved data is exposed by a {@link android.database.Cursor Cursor}
-object that can be used to iterate backward or forward through the result
-set. You can use this object only to read the data. To add, modify, or
-delete data, you must use a ContentResolver object.
-</p>
-
-
-<h3>Reading retrieved data</h3>
-
-<p>
-The Cursor object returned by a query provides access to a recordset of
-results. If you have queried for a specific record by ID, this set will
-contain only one value. Otherwise, it can contain multiple values.
-(If there are no matches, it can also be empty.) You
-can read data from specific fields in the record, but you must know the
-data type of the field, because the Cursor object has a separate method
-for reading each type of data — such as <code>{@link
-android.database.Cursor#getString getString()}</code>, <code>{@link
-android.database.Cursor#getInt getInt()}</code>, and <code>{@link
-android.database.Cursor#getFloat getFloat()}</code>.
-(However, for most types, if you call the method for reading strings,
-the Cursor object will give you the String representation of the data.)
-The Cursor lets you request the column name from the index of the column,
-or the index number from the column name.
-</p>
-
-<p>
-The following snippet demonstrates reading names and phone numbers from
-the query illustrated earlier:
-</p>
-
-<pre>
-import android.provider.Contacts.People;
-
-private void getColumnData(Cursor cur){
- if (cur.moveToFirst()) {
-
- String name;
- String phoneNumber;
- int nameColumn = cur.getColumnIndex(People.NAME);
- int phoneColumn = cur.getColumnIndex(People.NUMBER);
- String imagePath;
-
- do {
- // Get the field values
- name = cur.getString(nameColumn);
- phoneNumber = cur.getString(phoneColumn);
-
- // Do something with the values.
- ...
-
- } while (cur.moveToNext());
-
- }
-}
-</pre>
-
-<p>
-If a query can return binary data, such as an image or sound, the data
-may be directly entered in the table or the table entry for that data may be
-a string specifying a {@code content:} URI that you can use to get the data.
-In general, smaller amounts of data (say, from 20 to 50K or less) are most often
-directly entered in the table and can be read by calling
-<code>{@link android.database.Cursor#getBlob Cursor.getBlob()}</code>.
-It returns a byte array.
-</p>
-
-<p>
-If the table entry is a {@code content:} URI, you should never try to open
-and read the file directly (for one thing, permissions problems can make this
-fail). Instead, you should call
-<code>{@link android.content.ContentResolver#openInputStream
-ContentResolver.openInputStream()}</code> to get an
-{@link java.io.InputStream} object that you can use to read the data.
-</p>
-
-
-<h2><a name="modifying"></a>Modifying Data</h2>
-
-<p>
-Data kept by a content provider can be modified by:
-</p>
-
-<ul>
-<p><li>Adding new records</li>
-<li>Adding new values to existing records</li>
-<li>Batch updating existing records</li>
-<li>Deleting records</li>
-</ul>
-
-<p>
-All data modification is accomplished using {@link android.content.ContentResolver}
-methods. Some content providers require a more restrictive permission for writing
-data than they do for reading it. If you don't have permission to write to a
-content provider, the ContentResolver methods will fail.
-</p>
-
-
-<h3>Adding records</h3>
-
-<p>
-To add a new record to a content provider, first set up a map of key-value pairs
-in a {@link android.content.ContentValues} object, where each key matches
-the name of a column in the content provider and the value is the desired
-value for the new record in that column. Then call <code>{@link
-android.content.ContentResolver#insert ContentResolver.insert()}</code> and pass
-it the URI of the provider and the ContentValues map. This method returns
-the full URI of the new record — that is, the provider's URI with
-the appended ID for the new record. You can then use this URI to query and
-get a Cursor over the new record, and to further modify the record.
-Here's an example:
-</p>
-
-<pre>
-import android.provider.Contacts.People;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-
-ContentValues values = new ContentValues();
-
-// Add Abraham Lincoln to contacts and make him a favorite.
-values.put(People.NAME, "Abraham Lincoln");
-// 1 = the new contact is added to favorites
-// 0 = the new contact is not added to favorites
-values.put(People.STARRED, 1);
-
-Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
-</pre>
-
-
-<h3>Adding new values</h3>
-
-<p>
-Once a record exists, you can add new information to it or modify
-existing information. For example, the next step in the example above would
-be to add contact information — like a phone number or an IM or e-mail
-address — to the new entry.
-</p>
-
-<p>
-The best way to add to a record in the Contacts database is to append
-the name of the table where the new data goes to the URI for the
-record, then use the amended URI to add the new data values. Each
-Contacts table exposes a name for this purpose as a {@code
-CONTENT_DIRECTORY} constant. The following code continues the previous
-example by adding a phone number and e-mail address for the record
-just created:
-</p>
-
-<pre>
-Uri phoneUri = null;
-Uri emailUri = null;
-
-// Add a phone number for Abraham Lincoln. Begin with the URI for
-// the new record just returned by insert(); it ends with the _ID
-// of the new record, so we don't have to add the ID ourselves.
-// Then append the designation for the phone table to this URI,
-// and use the resulting URI to insert the phone number.
-phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
-
-values.clear();
-values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);
-values.put(People.Phones.NUMBER, "1233214567");
-getContentResolver().insert(phoneUri, values);
-
-// Now add an email address in the same way.
-emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);
-
-values.clear();
-// ContactMethods.KIND is used to distinguish different kinds of
-// contact methods, such as email, IM, etc.
-values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);
-values.put(People.ContactMethods.DATA, "test@example.com");
-values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME);
-getContentResolver().insert(emailUri, values);
-</pre>
-
-<p>
-You can place small amounts of binary data into a table by calling
-the version of <code>{@link android.content.ContentValues#put
-ContentValues.put()}</code> that takes a byte array.
-That would work for a small icon-like image or a short audio clip, for example.
-However, if you have a large amount of binary data to add, such as a photograph
-or a complete song, put a {@code content:} URI for the data in the table and call
-<code>{@link android.content.ContentResolver#openOutputStream
-ContentResolver.openOutputStream()}</code>
-with the file's URI. (That causes the content provider to store the data
-in a file and record the file path in a hidden field of the record.)
-</p>
-
-<p>
-In this regard, the {@link android.provider.MediaStore} content
-provider, the main provider that dispenses image, audio, and video
-data, employs a special convention: The same URI that is used with
-{@code query()} or {@code managedQuery()} to get meta-information
-about the binary data (such as, the caption of a photograph or the
-date it was taken) is used with {@code openInputStream()}
-to get the data itself. Similarly, the same URI that is used with
-{@code insert()} to put meta-information into a MediaStore record
-is used with {@code openOutputStream()} to place the binary data there.
-The following code snippet illustrates this convention:
-</p>
-
-<pre>
-import android.provider.MediaStore.Images.Media;
-import android.content.ContentValues;
-import java.io.OutputStream;
-
-// Save the name and description of an image in a ContentValues map.
-ContentValues values = new ContentValues(3);
-values.put(Media.DISPLAY_NAME, "road_trip_1");
-values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");
-values.put(Media.MIME_TYPE, "image/jpeg");
-
-// Add a new record without the bitmap, but with the values just set.
-// insert() returns the URI of the new record.
-Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
-
-// Now get a handle to the file for that record, and save the data into it.
-// Here, sourceBitmap is a Bitmap object representing the file to save to the database.
-try {
- OutputStream outStream = getContentResolver().openOutputStream(uri);
- sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
- outStream.close();
-} catch (Exception e) {
- Log.e(TAG, "exception while writing image", e);
-}
-</pre>
-
-
-<h3>Batch updating records</h3>
-
-<p>
-To batch update a group of records (for example, to change "NY" to "New York"
-in all fields), call the <code>{@link
-android.content.ContentResolver#update ContentResolver.update()}</code>
-method with the columns and values to change.
-</p>
-
-
-<h3><a name="deletingrecord"></a>Deleting a record</h3>
-
-<p>
-To delete a single record, call {<code>{@link
-android.content.ContentResolver#delete ContentResolver.delete()}</code>
-with the URI of a specific row.
-</p>
-
-<p>
-To delete multiple rows, call <code>{@link
-android.content.ContentResolver#delete ContentResolver.delete()}</code>
-with the URI of the type of record to delete (for example, {@code android.provider.Contacts.People.CONTENT_URI}) and an SQL {@code WHERE}
-clause defining which rows to delete. (<i><b>Caution</b>:
-Be sure to include a valid {@code WHERE} clause if you're deleting a general
-type, or you risk deleting more records than you intended!</i>).
-</p>
-
-
-<h2><a name="creating"></a>Creating a Content Provider</h2>
-
-<p>
-To create a content provider, you must:
-</p>
-
-<ul>
-<li>Set up a system for storing the data. Most content providers
-store their data using Android's file storage methods or SQLite databases,
-but you can store your data any way you want. Android provides the
-{@link android.database.sqlite.SQLiteOpenHelper SQLiteOpenHelper}
-class to help you create a database and {@link
-android.database.sqlite.SQLiteDatabase SQLiteDatabase} to manage it.</li>
-
-<li><p>Extend the {@link android.content.ContentProvider} class to provide
-access to the data.</p></li>
-
-<li><p>Declare the content provider in the manifest file for your
-application (AndroidManifest.xml).</p></li>
-</ul>
-
-<p>
-The following sections have notes on the last two of these tasks.
-</p>
-
-
-<h3>Extending the ContentProvider class</h3>
-
-<p>
-You define a {@link android.content.ContentProvider} subclass to
-expose your data to others using the conventions expected by
-ContentResolver and Cursor objects. Principally, this means
-implementing six abstract methods declared in the ContentProvider class:
-</p>
-
-<p style="margin-left: 2em">{@code query()}
-<br/>{@code insert()}
-<br/>{@code update()}
-<br/>{@code delete()}
-<br/>{@code getType()}
-<br/>{@code onCreate()}</p>
-
-<p>
-The {@code query()} method must return a {@link android.database.Cursor} object
-that can iterate over the requested data. Cursor itself is an interface, but
-Android provides some ready-made Cursor objects that you can use. For example,
-{@link android.database.sqlite.SQLiteCursor} can iterate over data stored in
-an SQLite database. You get the Cursor object by calling any of the {@link
-android.database.sqlite.SQLiteDatabase SQLiteDatabase} class's {@code query()}
-methods. There are other Cursor implementations — such as {@link
-android.database.MatrixCursor} — for data not stored in a database.
-</p>
-
-<p>
-Because these ContentProvider methods can be called from
-various ContentResolver objects in different processes and threads,
-they must be implemented in a thread-safe manner.
-</p>
-
-<p>
-As a courtesy, you might also want to call <code>{@link android.content.ContentResolver#notifyChange(android.net.Uri,android.database.ContentObserver)
-ContentResolver.notifyChange()}</code> to notify listeners when there are
-modifications to the data.
-</p>
-
-<p>
-Beyond defining the subclass itself, there are other steps you should take
-to simplify the work of clients and make the class more accessible:
-</p>
-
-<ul>
-<li>Define a {@code public static final} {@link android.net.Uri}
-named {@code CONTENT_URI}. This is the string that represents the full
-{@code content:} URI that your content provider handles. You must define a
-unique string for this value. The best solution is to use the fully-qualified
-class name of the content provider (made lowercase). So, for example, the
-URI for a TransportationProvider class could be defined as follows:
-
-<pre>public static final Uri CONTENT_URI =
- Uri.parse("content://com.example.codelab.transportationprovider");</pre>
-
-<p>
-If the provider has subtables, also define {@code CONTENT_URI} constants for
-each of the subtables. These URIs should all have the same authority (since
-that identifies the content provider), and be distinguished only by their paths.
-For example:
-</p>
-
-<p style="margin-left: 2em">{@code content://com.example.codelab.transportationprovider/train}
-<br/>{@code content://com.example.codelab.transportationprovider/air/domestic}
-<br/>{@code content://com.example.codelab.transportationprovider/air/international}</p>
-
-<p>
-For an overview of {@code content:} URIs, see the <a href="#urisum">Content URI
-Summary</a> at the end of this document.
-</p></li>
-
-<li><p>Define the column names that the content provider will return to clients.
-If you are using an underlying database, these column names are typically
-identical to the SQL database column names they represent. Also define
-{@code public static} String constants that clients can use to specify
-the columns in queries and other instructions.
-</p>
-
-<p>
-Be sure to include an integer column named "{@code _id}"
-(with the constant {@code _ID}) for
-the IDs of the records. You should have this field whether or not you have
-another field (such as a URL) that is also unique among all records. If
-you're using the SQLite database, the {@code _ID} field should be the
-following type:
-</p>
-
-<p style="margin-left: 2em">{@code INTEGER PRIMARY KEY AUTOINCREMENT}</p>
-
-<p>
-The {@code AUTOINCREMENT} descriptor is optional. But without it, SQLite
-increments an ID counter field to the next number above the largest
-existing number in the column. If you delete the last row, the next row added
-will have the same ID as the deleted row. {@code AUTOINCREMENT} avoids this
-by having SQLite increment to the next largest value whether deleted or not.
-</p>
-</li>
-
-<li><p>Carefully document the data type of each column. Clients need this
-information to read the data.</p></li>
-
-<li><p>If you are handling a new data type, you must define a new MIME type
-to return in your implementation of <code>{@link
-android.content.ContentProvider#getType ContentProvider.getType()}</code>.
-The type depends in part on whether or not the {@code content:} URI submitted
-to {@code getType()} limits the request to a specific record. There's one
-form of the MIME type for a single record and another for multiple records.
-Use the {@link android.net.Uri Uri} methods to help determine what is being
-requested. Here is the general format for each type:</p></li>
-
-<ul>
-<li><p>For a single record: {@code vnd.android.cursor.item/vnd.<em>yourcompanyname.contenttype</em>}</p>
-
-<p>For example, a request for train record 122, like this URI,</p>
-<p style="margin-left: 2em">{@code content://com.example.transportationprovider/trains/122}</p>
-
-<p>might return this MIME type:</p>
-<p style="margin-left: 2em">{@code vnd.android.cursor.item/vnd.example.rail}</p>
-</li>
-
-<li><p>For multiple records: {@code vnd.android.cursor.dir/vnd.<em>yourcompanyname.contenttype</em>}</p>
-
-<p>For example, a request for all train records, like the following URI,</p>
-<p style="margin-left: 2em">{@code content://com.example.transportationprovider/trains}</p>
-
-<p>might return this MIME type:</p>
-<p style="margin-left: 2em">{@code vnd.android.cursor.dir/vnd.example.rail}</p>
-</li>
-</ul>
-
-<li><p>If you are exposing byte data that's too big to put in the table itself
-— such as a large bitmap file — the field that exposes the
-data to clients should actually contain a {@code content:} URI string.
-This is the field that gives clients access to the data file. The record
-should also have another field, named "{@code _data}" that lists the exact file
-path on the device for that file. This field is not intended to be read by
-the client, but by the ContentResolver. The client will call <code>{@link
-android.content.ContentResolver#openInputStream ContentResolver.openInputStream()}</code>
-on the user-facing field holding the URI for the item. The ContentResolver
-will request the "{@code _data}" field for that record, and because
-it has higher permissions than a client, it should be able to access
-that file directly and return a read wrapper for the file to the client.</p></li>
-
-</ul>
-
-<p>
-For an example of a private content provider implementation, see the
-NodePadProvider class in the Notepad sample application that ships with the SDK.
-</p>
-
-
-<h3>Declaring the content provider</h3>
-
-<p>
-To let the Android system know about the content provider you've developed,
-declare it with a {@code <provider>} element in the application's
-AndroidManifest.xml file. Content providers that are not declared in the
-manifest are not visible to the Android system
-</p>
-
-<p>
-The {@code name} attribute is the fully qualified name of the ContentProvider
-subclass. The {@code authorities} attribute is the authority part of the
-{@code content:} URI that identifies the provider.
-For example if the ContentProvider subclass is AutoInfoProvider, the
-{@code <provider>} element might look like this:
-</p>
-
-<pre>
-<provider android:name="com.example.autos.AutoInfoProvider"
- android:authorities="com.example.autos.autoinfoprovider"
- . . . />
-</provider>
-</pre>
-
-<p>
-Note that the {@code authorities} attribute omits the path part of a
-{@code content:} URI. For example, if AutoInfoProvider controlled subtables
-for different types of autos or different manufacturers,
-</p>
-
-<p style="margin-left: 2em">{@code content://com.example.autos.autoinfoprovider/honda}
-<br/>{@code content://com.example.autos.autoinfoprovider/gm/compact}
-<br/>{@code content://com.example.autos.autoinfoprovider/gm/suv}</p>
-
-<p>
-those paths would not be declared in the manifest. The authority is what
-identifies the provider, not the path; your provider can interpret the path
-part of the URI in any way you choose.
-</p>
-
-<p>
-Other {@code <provider>} attributes can set permissions to read and
-write data, provide for an icon and text that can be displayed to users,
-enable and disable the provider, and so on. Set the {@code multiprocess}
-attribute to "{@code true}" if data does not need to be synchronized between
-multiple running versions of the content provider. This permits an instance
-of the provider to be created in each client process, eliminating the need
-to perform IPC.
-</p>
-
-
-<h2><a name="urisum"></a>Content URI Summary</h2>
-
-<p>
-Here is a recap of the important parts of a content URI:
-</p>
-
-<p>
-<img src="{@docRoot}images/content_uri.png" alt="Elements of a content URI"
-height="80" width="528">
-</p>
-
-<ol type="A">
-<li>Standard prefix indicating that the data is controlled by a
-content provider. It's never modified.</li>
-
-<li><p>The authority part of the URI; it identifies the content provider.
-For third-party applications, this should be a fully-qualified class name
-(reduced to lowercase) to ensure uniqueness. The authority is declared in
-the {@code <provider>} element's {@code authorities} attribute:</p>
-
-<pre><provider android:name=".TransportationProvider"
- android:authorities="com.example.transportationprovider"
- . . . ></pre></li>
-
-<li><p>The path that the content provider uses to determine what kind of data is
-being requested. This can be zero or more segments long. If the content provider
-exposes only one type of data (only trains, for example), it can be absent.
-If the provider exposes several types, including subtypes, it can be several
-segments long — for example, "{@code land/bus}", "{@code land/train}",
-"{@code sea/ship}", and "{@code sea/submarine}" to give four possibilities.</p></li>
-
-<li><p>The ID of the specific record being requested, if any. This is the
-{@code _ID} value of the requested record. If the request is not limited to
-a single record, this segment and the trailing slash are omitted:</p>
-
-<p style="margin-left: 2em">{@code content://com.example.transportationprovider/trains}</p>
-</li>
-</ol>
-
-
+<dl>
+ <dt>
+ <strong><a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+ Content Provider Basics</a></strong>
+ </dt>
+ <dd>
+ How to access data in a content provider when the data is organized in tables.
+ </dd>
+ <dt>
+ <strong><a href="{@docRoot}guide/topics/providers/content-provider-creating.html">
+ Creating a Content Provider</a></strong>
+ </dt>
+ <dd>
+ How to create your own content provider.
+ </dd>
+ <dt>
+ <strong><a href="{@docRoot}guide/topics/providers/calendar-provider.html">
+ Calendar Provider</a></strong>
+ </dt>
+ <dd>
+ How to access the Calendar Provider that is part of the Android platform.
+ </dd>
+</dl>
diff --git a/docs/html/images/publishing/publishing_android_market.png b/docs/html/images/publishing/publishing_android_market.png
new file mode 100755
index 0000000..aa591ef
--- /dev/null
+++ b/docs/html/images/publishing/publishing_android_market.png
Binary files differ
diff --git a/docs/html/shareables/adl/2010Q2_Business_Overview.pdf b/docs/html/shareables/adl/2010Q2_Business_Overview.pdf
deleted file mode 100644
index c34ea15..0000000
--- a/docs/html/shareables/adl/2010Q2_Business_Overview.pdf
+++ /dev/null
Binary files differ
diff --git a/docs/html/shareables/adl/2010Q2_Market_Overview.pdf b/docs/html/shareables/adl/2010Q2_Market_Overview.pdf
deleted file mode 100644
index 3752258..0000000
--- a/docs/html/shareables/adl/2010Q2_Market_Overview.pdf
+++ /dev/null
Binary files differ
diff --git a/docs/html/shareables/adl/2010Q2_SDK_Overview.pdf b/docs/html/shareables/adl/2010Q2_SDK_Overview.pdf
deleted file mode 100644
index 8796584..0000000
--- a/docs/html/shareables/adl/2010Q2_SDK_Overview.pdf
+++ /dev/null
Binary files differ
diff --git a/docs/html/shareables/adl/2011Q3_Android_Market_for_Developers.pdf b/docs/html/shareables/adl/2011Q3_Android_Market_for_Developers.pdf
deleted file mode 100644
index 598a27e..0000000
--- a/docs/html/shareables/adl/2011Q3_Android_Market_for_Developers.pdf
+++ /dev/null
Binary files differ
diff --git a/docs/html/shareables/adl/2011Q3_Designing_UIs_for_Phones_and_Tablets.pdf b/docs/html/shareables/adl/2011Q3_Designing_UIs_for_Phones_and_Tablets.pdf
deleted file mode 100644
index 6ef41dd..0000000
--- a/docs/html/shareables/adl/2011Q3_Designing_UIs_for_Phones_and_Tablets.pdf
+++ /dev/null
Binary files differ
diff --git a/docs/html/shareables/adl/2011Q3_Introduction_to_Honeycomb_APIs.pdf b/docs/html/shareables/adl/2011Q3_Introduction_to_Honeycomb_APIs.pdf
deleted file mode 100755
index e56d2377..0000000
--- a/docs/html/shareables/adl/2011Q3_Introduction_to_Honeycomb_APIs.pdf
+++ /dev/null
Binary files differ
diff --git a/docs/html/shareables/icon_templates-v1.0.zip b/docs/html/shareables/icon_templates-v1.0.zip
deleted file mode 100644
index 94fbcdc..0000000
--- a/docs/html/shareables/icon_templates-v1.0.zip
+++ /dev/null
Binary files differ
diff --git a/graphics/java/android/graphics/AvoidXfermode.java b/graphics/java/android/graphics/AvoidXfermode.java
index 7e2722d..5a59e36 100644
--- a/graphics/java/android/graphics/AvoidXfermode.java
+++ b/graphics/java/android/graphics/AvoidXfermode.java
@@ -20,6 +20,7 @@
* AvoidXfermode xfermode will draw the src everywhere except on top of the
* opColor or, depending on the Mode, draw only on top of the opColor.
*/
+@Deprecated
public class AvoidXfermode extends Xfermode {
// these need to match the enum in SkAvoidXfermode.h on the native side
diff --git a/graphics/java/android/graphics/PaintFlagsDrawFilter.java b/graphics/java/android/graphics/PaintFlagsDrawFilter.java
index 0f0d03d..c833a12 100644
--- a/graphics/java/android/graphics/PaintFlagsDrawFilter.java
+++ b/graphics/java/android/graphics/PaintFlagsDrawFilter.java
@@ -17,6 +17,10 @@
package android.graphics;
public class PaintFlagsDrawFilter extends DrawFilter {
+ /** @hide **/
+ public final int clearBits;
+ /** @hide **/
+ public final int setBits;
/**
* Subclass of DrawFilter that affects every paint by first clearing
@@ -27,6 +31,8 @@
* @param setBits These bits will be set in the paint's flags
*/
public PaintFlagsDrawFilter(int clearBits, int setBits) {
+ this.clearBits = clearBits;
+ this.setBits = setBits;
// our native constructor can return 0, if the specified bits
// are effectively a no-op
mNativeInt = nativeConstructor(clearBits, setBits);
diff --git a/graphics/java/android/graphics/PixelXorXfermode.java b/graphics/java/android/graphics/PixelXorXfermode.java
index 18d15cf..6075ec39 100644
--- a/graphics/java/android/graphics/PixelXorXfermode.java
+++ b/graphics/java/android/graphics/PixelXorXfermode.java
@@ -22,6 +22,7 @@
* this mode *always* returns an opaque color (alpha == 255). Thus it is
* not really usefull for operating on blended colors.
*/
+@Deprecated
public class PixelXorXfermode extends Xfermode {
public PixelXorXfermode(int opColor) {
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index 84f8282..4c30e04 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -172,6 +172,7 @@
uint32_t mFlags;
bool mIsEncoder;
+ bool mIsVideo;
char *mMIME;
char *mComponentName;
sp<MetaData> mOutputFormat;
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 46420c1..b741ed6 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -843,6 +843,8 @@
DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM,
DENSITY_TV = ACONFIGURATION_DENSITY_TV,
DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH,
+ DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH,
+ DENSITY_XXHIGH = ACONFIGURATION_DENSITY_XXHIGH,
DENSITY_NONE = ACONFIGURATION_DENSITY_NONE
};
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 824ab4f..ee935e4 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -67,6 +67,8 @@
"SetupColorFilter",
"ResetShadow",
"SetupShadow",
+ "ResetPaintFilter",
+ "SetupPaintFilter",
"DrawGLFunction"
};
@@ -249,7 +251,7 @@
float f2 = getFloat();
float f3 = getFloat();
float f4 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
int flags = getInt();
ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p, 0x%x", (char*) indent,
OP_NAMES[op], f1, f2, f3, f4, paint, flags);
@@ -322,7 +324,7 @@
Layer* layer = (Layer*) getInt();
float x = getFloat();
float y = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
layer, x, y, paint);
}
@@ -331,7 +333,7 @@
SkBitmap* bitmap = getBitmap();
float x = getFloat();
float y = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
bitmap, x, y, paint);
}
@@ -339,7 +341,7 @@
case DrawBitmapMatrix: {
SkBitmap* bitmap = getBitmap();
SkMatrix* matrix = getMatrix();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %p, %p, %p", (char*) indent, OP_NAMES[op],
bitmap, matrix, paint);
}
@@ -354,7 +356,7 @@
float f6 = getFloat();
float f7 = getFloat();
float f8 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %p, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %p",
(char*) indent, OP_NAMES[op], bitmap, f1, f2, f3, f4, f5, f6, f7, f8, paint);
}
@@ -368,7 +370,7 @@
float* vertices = getFloats(verticesCount);
bool hasColors = getInt();
int* colors = hasColors ? getInts(colorsCount) : NULL;
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
}
break;
@@ -387,7 +389,7 @@
float top = getFloat();
float right = getFloat();
float bottom = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %.2f, %.2f, %.2f, %.2f", (char*) indent, OP_NAMES[op],
left, top, right, bottom);
}
@@ -403,7 +405,7 @@
float f2 = getFloat();
float f3 = getFloat();
float f4 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
f1, f2, f3, f4, paint);
}
@@ -415,7 +417,7 @@
float f4 = getFloat();
float f5 = getFloat();
float f6 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %p",
(char*) indent, OP_NAMES[op], f1, f2, f3, f4, f5, f6, paint);
}
@@ -424,7 +426,7 @@
float f1 = getFloat();
float f2 = getFloat();
float f3 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %.2f, %.2f, %.2f, %p",
(char*) indent, OP_NAMES[op], f1, f2, f3, paint);
}
@@ -434,7 +436,7 @@
float f2 = getFloat();
float f3 = getFloat();
float f4 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p",
(char*) indent, OP_NAMES[op], f1, f2, f3, f4, paint);
}
@@ -447,28 +449,28 @@
float f5 = getFloat();
float f6 = getFloat();
int i1 = getInt();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %d, %p",
(char*) indent, OP_NAMES[op], f1, f2, f3, f4, f5, f6, i1, paint);
}
break;
case DrawPath: {
SkPath* path = getPath();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %p, %p", (char*) indent, OP_NAMES[op], path, paint);
}
break;
case DrawLines: {
int count = 0;
float* points = getFloats(count);
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
}
break;
case DrawPoints: {
int count = 0;
float* points = getFloats(count);
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
}
break;
@@ -477,7 +479,7 @@
int count = getInt();
float x = getFloat();
float y = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
float length = getFloat();
ALOGD("%s%s %s, %d, %d, %.2f, %.2f, %p, %.2f", (char*) indent, OP_NAMES[op],
text.text(), text.length(), count, x, y, paint, length);
@@ -488,7 +490,7 @@
int count = getInt();
int positionsCount = 0;
float* positions = getFloats(positionsCount);
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
ALOGD("%s%s %s, %d, %d, %p", (char*) indent, OP_NAMES[op],
text.text(), text.length(), count, paint);
}
@@ -523,6 +525,16 @@
radius, dx, dy, color);
}
break;
+ case ResetPaintFilter: {
+ ALOGD("%s%s", (char*) indent, OP_NAMES[op]);
+ }
+ break;
+ case SetupPaintFilter: {
+ int clearBits = getInt();
+ int setBits = getInt();
+ ALOGD("%s%s 0x%x, 0x%x", (char*) indent, OP_NAMES[op], clearBits, setBits);
+ }
+ break;
default:
ALOGD("Display List error: op not handled: %s%s",
(char*) indent, OP_NAMES[op]);
@@ -588,7 +600,7 @@
float f2 = getFloat();
float f3 = getFloat();
float f4 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
int flags = getInt();
DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p, 0x%x", (char*) indent,
OP_NAMES[op], f1, f2, f3, f4, paint, flags);
@@ -671,7 +683,7 @@
Layer* layer = (Layer*) getInt();
float x = getFloat();
float y = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
layer, x, y, paint);
renderer.drawLayer(layer, x, y, paint);
@@ -681,7 +693,7 @@
SkBitmap* bitmap = getBitmap();
float x = getFloat();
float y = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
bitmap, x, y, paint);
renderer.drawBitmap(bitmap, x, y, paint);
@@ -690,7 +702,7 @@
case DrawBitmapMatrix: {
SkBitmap* bitmap = getBitmap();
SkMatrix* matrix = getMatrix();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %p, %p, %p", (char*) indent, OP_NAMES[op],
bitmap, matrix, paint);
renderer.drawBitmap(bitmap, matrix, paint);
@@ -706,7 +718,7 @@
float f6 = getFloat();
float f7 = getFloat();
float f8 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %p",
(char*) indent, OP_NAMES[op], bitmap, f1, f2, f3, f4, f5, f6, f7, f8, paint);
renderer.drawBitmap(bitmap, f1, f2, f3, f4, f5, f6, f7, f8, paint);
@@ -722,7 +734,7 @@
float* vertices = getFloats(verticesCount);
bool hasColors = getInt();
int* colors = hasColors ? getInts(colorsCount) : NULL;
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
renderer.drawBitmapMesh(bitmap, meshWidth, meshHeight, vertices, colors, paint);
@@ -746,7 +758,7 @@
float top = getFloat();
float right = getFloat();
float bottom = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
renderer.drawPatch(bitmap, xDivs, yDivs, colors, xDivsCount, yDivsCount,
@@ -765,7 +777,7 @@
float f2 = getFloat();
float f3 = getFloat();
float f4 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
f1, f2, f3, f4, paint);
renderer.drawRect(f1, f2, f3, f4, paint);
@@ -778,7 +790,7 @@
float f4 = getFloat();
float f5 = getFloat();
float f6 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %p",
(char*) indent, OP_NAMES[op], f1, f2, f3, f4, f5, f6, paint);
renderer.drawRoundRect(f1, f2, f3, f4, f5, f6, paint);
@@ -788,7 +800,7 @@
float f1 = getFloat();
float f2 = getFloat();
float f3 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %p",
(char*) indent, OP_NAMES[op], f1, f2, f3, paint);
renderer.drawCircle(f1, f2, f3, paint);
@@ -799,7 +811,7 @@
float f2 = getFloat();
float f3 = getFloat();
float f4 = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %p",
(char*) indent, OP_NAMES[op], f1, f2, f3, f4, paint);
renderer.drawOval(f1, f2, f3, f4, paint);
@@ -813,7 +825,7 @@
float f5 = getFloat();
float f6 = getFloat();
int i1 = getInt();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %d, %p",
(char*) indent, OP_NAMES[op], f1, f2, f3, f4, f5, f6, i1, paint);
renderer.drawArc(f1, f2, f3, f4, f5, f6, i1 == 1, paint);
@@ -821,7 +833,7 @@
break;
case DrawPath: {
SkPath* path = getPath();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %p, %p", (char*) indent, OP_NAMES[op], path, paint);
renderer.drawPath(path, paint);
}
@@ -829,7 +841,7 @@
case DrawLines: {
int count = 0;
float* points = getFloats(count);
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
renderer.drawLines(points, count, paint);
}
@@ -837,7 +849,7 @@
case DrawPoints: {
int count = 0;
float* points = getFloats(count);
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
renderer.drawPoints(points, count, paint);
}
@@ -847,7 +859,7 @@
int count = getInt();
float x = getFloat();
float y = getFloat();
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
float length = getFloat();
DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %.2f, %.2f, %p, %.2f", (char*) indent,
OP_NAMES[op], text.text(), text.length(), count, x, y, paint, length);
@@ -859,7 +871,7 @@
int count = getInt();
int positionsCount = 0;
float* positions = getFloats(positionsCount);
- SkPaint* paint = getPaint();
+ SkPaint* paint = getPaint(renderer);
DISPLAY_LIST_LOGD("%s%s %s, %d, %d, %p", (char*) indent,
OP_NAMES[op], text.text(), text.length(), count, paint);
renderer.drawPosText(text.text(), text.length(), count, positions, paint);
@@ -902,6 +914,19 @@
renderer.setupShadow(radius, dx, dy, color);
}
break;
+ case ResetPaintFilter: {
+ DISPLAY_LIST_LOGD("%s%s", (char*) indent, OP_NAMES[op]);
+ renderer.resetPaintFilter();
+ }
+ break;
+ case SetupPaintFilter: {
+ int clearBits = getInt();
+ int setBits = getInt();
+ DISPLAY_LIST_LOGD("%s%s 0x%x, 0x%x", (char*) indent, OP_NAMES[op],
+ clearBits, setBits);
+ renderer.setupPaintFilter(clearBits, setBits);
+ }
+ break;
default:
DISPLAY_LIST_LOGD("Display List error: op not handled: %s%s",
(char*) indent, OP_NAMES[op]);
@@ -1277,5 +1302,15 @@
addInt(color);
}
+void DisplayListRenderer::resetPaintFilter() {
+ addOp(DisplayList::ResetPaintFilter);
+}
+
+void DisplayListRenderer::setupPaintFilter(int clearBits, int setBits) {
+ addOp(DisplayList::SetupPaintFilter);
+ addInt(clearBits);
+ addInt(setBits);
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 422184e..6fe4205 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -103,6 +103,8 @@
SetupColorFilter,
ResetShadow,
SetupShadow,
+ ResetPaintFilter,
+ SetupPaintFilter,
DrawGLFunction,
};
@@ -177,8 +179,8 @@
return (SkPath*) getInt();
}
- SkPaint* getPaint() {
- return (SkPaint*) getInt();
+ SkPaint* getPaint(OpenGLRenderer& renderer) {
+ return renderer.filterPaint((SkPaint*) getInt());
}
DisplayList* getDisplayList() {
@@ -304,6 +306,9 @@
virtual void resetShadow();
virtual void setupShadow(float radius, float dx, float dy, int color);
+ virtual void resetPaintFilter();
+ virtual void setupPaintFilter(int clearBits, int setBits);
+
ANDROID_API void reset();
const SkWriter32& writeStream() const {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 786a4f1..cc0e05e 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -111,6 +111,7 @@
mShader = NULL;
mColorFilter = NULL;
mHasShadow = false;
+ mHasDrawFilter = false;
memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
@@ -2399,6 +2400,31 @@
}
///////////////////////////////////////////////////////////////////////////////
+// Draw filters
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::resetPaintFilter() {
+ mHasDrawFilter = false;
+}
+
+void OpenGLRenderer::setupPaintFilter(int clearBits, int setBits) {
+ mHasDrawFilter = true;
+ mPaintFilterClearBits = clearBits & SkPaint::kAllFlags;
+ mPaintFilterSetBits = setBits & SkPaint::kAllFlags;
+}
+
+SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint) {
+ if (!mHasDrawFilter || !paint) return paint;
+
+ uint32_t flags = paint->getFlags();
+
+ mFilteredPaint = *paint;
+ mFilteredPaint.setFlags((flags & ~mPaintFilterClearBits) | mPaintFilterSetBits);
+
+ return &mFilteredPaint;
+}
+
+///////////////////////////////////////////////////////////////////////////////
// Drawing implementation
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index a9cda47..ae355bb 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -136,6 +136,11 @@
virtual void resetShadow();
virtual void setupShadow(float radius, float dx, float dy, int color);
+ virtual void resetPaintFilter();
+ virtual void setupPaintFilter(int clearBits, int setBits);
+
+ SkPaint* filterPaint(SkPaint* paint);
+
protected:
/**
* Compose the layer defined in the current snapshot with the layer
@@ -581,6 +586,12 @@
float mShadowDy;
int mShadowColor;
+ // Draw filters
+ bool mHasDrawFilter;
+ int mPaintFilterClearBits;
+ int mPaintFilterSetBits;
+ SkPaint mFilteredPaint;
+
// Various caches
Caches& mCaches;
diff --git a/libs/rs/driver/rsdRuntimeMath.cpp b/libs/rs/driver/rsdRuntimeMath.cpp
index e315539..753ef73 100644
--- a/libs/rs/driver/rsdRuntimeMath.cpp
+++ b/libs/rs/driver/rsdRuntimeMath.cpp
@@ -329,6 +329,16 @@
return prev;
}
+static uint32_t SC_AtomicUMin(volatile uint32_t *ptr, uint32_t value) {
+ uint32_t prev, status;
+ do {
+ prev = *ptr;
+ uint32_t n = rsMin(value, prev);
+ status = android_atomic_release_cas((int32_t) prev, (int32_t)n, (volatile int32_t*) ptr);
+ } while (CC_UNLIKELY(status != 0));
+ return prev;
+}
+
static int32_t SC_AtomicMin(volatile int32_t *ptr, int32_t value) {
int32_t prev, status;
do {
@@ -339,6 +349,16 @@
return prev;
}
+static uint32_t SC_AtomicUMax(volatile uint32_t *ptr, uint32_t value) {
+ uint32_t prev, status;
+ do {
+ prev = *ptr;
+ uint32_t n = rsMax(value, prev);
+ status = android_atomic_release_cas((int32_t) prev, (int32_t) n, (volatile int32_t*) ptr);
+ } while (CC_UNLIKELY(status != 0));
+ return prev;
+}
+
static int32_t SC_AtomicMax(volatile int32_t *ptr, int32_t value) {
int32_t prev, status;
do {
@@ -524,9 +544,9 @@
{ "_Z11rsAtomicXorPVii", (void *)&SC_AtomicXor, true },
{ "_Z11rsAtomicXorPVjj", (void *)&SC_AtomicXor, true },
{ "_Z11rsAtomicMinPVii", (void *)&SC_AtomicMin, true },
- { "_Z11rsAtomicMinPVjj", (void *)&SC_AtomicMin, true },
+ { "_Z11rsAtomicMinPVjj", (void *)&SC_AtomicUMin, true },
{ "_Z11rsAtomicMaxPVii", (void *)&SC_AtomicMax, true },
- { "_Z11rsAtomicMaxPVjj", (void *)&SC_AtomicMax, true },
+ { "_Z11rsAtomicMaxPVjj", (void *)&SC_AtomicUMax, true },
{ "_Z11rsAtomicCasPViii", (void *)&SC_AtomicCas, true },
{ "_Z11rsAtomicCasPVjjj", (void *)&SC_AtomicCas, true },
diff --git a/libs/rs/scriptc/rs_atomic.rsh b/libs/rs/scriptc/rs_atomic.rsh
index 87c6c02..a455edd 100644
--- a/libs/rs/scriptc/rs_atomic.rsh
+++ b/libs/rs/scriptc/rs_atomic.rsh
@@ -242,7 +242,7 @@
* @return old value
*/
extern uint32_t __attribute__((overloadable))
- rsAtomicCas(volatile uint32_t* addr, int32_t compareValue, int32_t newValue);
+ rsAtomicCas(volatile uint32_t* addr, uint32_t compareValue, uint32_t newValue);
#endif //defined(RS_VERSION) && (RS_VERSION >= 14)
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 7b2f1b7..7d4c282 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -840,7 +840,10 @@
/**
* Stops playing the audio data.
- *
+ * When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing
+ * after the last buffer that was written has been played. For an immediate stop, use
+ * {@link #pause()}, followed by {@link #flush()} to discard audio data that hasn't been played
+ * back yet.
* @throws IllegalStateException
*/
public void stop()
@@ -859,7 +862,7 @@
/**
* Pauses the playback of the audio data. Data that has not been played
* back will not be discarded. Subsequent calls to {@link #play} will play
- * this data back.
+ * this data back. See {@link #flush()} to discard this data.
*
* @throws IllegalStateException
*/
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 0aeb515..4d61067 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -77,7 +77,6 @@
LOCAL_STATIC_LIBRARIES := \
libstagefright_color_conversion \
- libstagefright_aacenc \
libstagefright_amrnbenc \
libstagefright_amrwbenc \
libstagefright_avcenc \
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 60d9bb7..7597f64 100755
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -18,7 +18,6 @@
#define LOG_TAG "OMXCodec"
#include <utils/Log.h>
-#include "include/AACEncoder.h"
#include "include/AMRNBEncoder.h"
#include "include/AMRWBEncoder.h"
#include "include/AVCEncoder.h"
@@ -73,7 +72,6 @@
FACTORY_CREATE_ENCODER(AMRNBEncoder)
FACTORY_CREATE_ENCODER(AMRWBEncoder)
-FACTORY_CREATE_ENCODER(AACEncoder)
FACTORY_CREATE_ENCODER(AVCEncoder)
FACTORY_CREATE_ENCODER(M4vH263Encoder)
@@ -88,7 +86,6 @@
static const FactoryInfo kFactoryInfo[] = {
FACTORY_REF(AMRNBEncoder)
FACTORY_REF(AMRWBEncoder)
- FACTORY_REF(AACEncoder)
FACTORY_REF(AVCEncoder)
FACTORY_REF(M4vH263Encoder)
};
@@ -153,7 +150,7 @@
{ MEDIA_MIMETYPE_AUDIO_AMR_WB, "OMX.TI.WBAMR.encode" },
{ MEDIA_MIMETYPE_AUDIO_AMR_WB, "AMRWBEncoder" },
{ MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.encode" },
- { MEDIA_MIMETYPE_AUDIO_AAC, "AACEncoder" },
+ { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.google.aac.encoder" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.DUCATI1.VIDEO.MPEG4E" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.encoder.mpeg4" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.encoder.mpeg4" },
@@ -1487,6 +1484,7 @@
mQuirks(quirks),
mFlags(flags),
mIsEncoder(isEncoder),
+ mIsVideo(!strncasecmp("video/", mime, 6)),
mMIME(strdup(mime)),
mComponentName(strdup(componentName)),
mSource(source),
@@ -2192,7 +2190,7 @@
}
int64_t OMXCodec::retrieveDecodingTimeUs(bool isCodecSpecific) {
- CHECK(mIsEncoder);
+ CHECK(mIsEncoder && mIsVideo);
if (mDecodingTimeList.empty()) {
CHECK(mSignalledEOS || mNoMoreOutputData);
@@ -2387,7 +2385,7 @@
mNoMoreOutputData = true;
}
- if (mIsEncoder) {
+ if (mIsEncoder && mIsVideo) {
int64_t decodingTimeUs = retrieveDecodingTimeUs(isCodecSpecific);
buffer->meta_data()->setInt64(kKeyDecodingTime, decodingTimeUs);
}
@@ -3249,7 +3247,7 @@
int64_t lastBufferTimeUs;
CHECK(srcBuffer->meta_data()->findInt64(kKeyTime, &lastBufferTimeUs));
CHECK(lastBufferTimeUs >= 0);
- if (mIsEncoder) {
+ if (mIsEncoder && mIsVideo) {
mDecodingTimeList.push_back(lastBufferTimeUs);
}
diff --git a/media/libstagefright/codecs/aacenc/Android.mk b/media/libstagefright/codecs/aacenc/Android.mk
index 8318ba4..34a2796 100644
--- a/media/libstagefright/codecs/aacenc/Android.mk
+++ b/media/libstagefright/codecs/aacenc/Android.mk
@@ -85,3 +85,29 @@
endif
include $(BUILD_STATIC_LIBRARY)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ SoftAACEncoder.cpp
+
+LOCAL_C_INCLUDES := \
+ frameworks/base/media/libstagefright/include \
+ frameworks/base/include/media/stagefright/openmax \
+ frameworks/base/media/libstagefright/codecs/common/include \
+
+LOCAL_CFLAGS := -DOSCL_IMPORT_REF=
+
+LOCAL_STATIC_LIBRARIES := \
+ libstagefright_aacenc
+
+LOCAL_SHARED_LIBRARIES := \
+ libstagefright_omx libstagefright_foundation libutils \
+ libstagefright_enc_common
+
+LOCAL_MODULE := libstagefright_soft_aacenc
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libstagefright/codecs/aacenc/SoftAACEncoder.cpp b/media/libstagefright/codecs/aacenc/SoftAACEncoder.cpp
new file mode 100644
index 0000000..c6724c26
--- /dev/null
+++ b/media/libstagefright/codecs/aacenc/SoftAACEncoder.cpp
@@ -0,0 +1,560 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SoftAACEncoder"
+#include <utils/Log.h>
+
+#include "SoftAACEncoder.h"
+
+#include "voAAC.h"
+#include "cmnMemory.h"
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/hexdump.h>
+
+namespace android {
+
+template<class T>
+static void InitOMXParams(T *params) {
+ params->nSize = sizeof(T);
+ params->nVersion.s.nVersionMajor = 1;
+ params->nVersion.s.nVersionMinor = 0;
+ params->nVersion.s.nRevision = 0;
+ params->nVersion.s.nStep = 0;
+}
+
+SoftAACEncoder::SoftAACEncoder(
+ const char *name,
+ const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData,
+ OMX_COMPONENTTYPE **component)
+ : SimpleSoftOMXComponent(name, callbacks, appData, component),
+ mEncoderHandle(NULL),
+ mApiHandle(NULL),
+ mMemOperator(NULL),
+ mNumChannels(1),
+ mSampleRate(44100),
+ mBitRate(0),
+ mSentCodecSpecificData(false),
+ mInputSize(0),
+ mInputFrame(NULL),
+ mInputTimeUs(-1ll),
+ mSawInputEOS(false),
+ mSignalledError(false) {
+ initPorts();
+ CHECK_EQ(initEncoder(), (status_t)OK);
+
+ setAudioParams();
+}
+
+SoftAACEncoder::~SoftAACEncoder() {
+ delete[] mInputFrame;
+ mInputFrame = NULL;
+
+ if (mEncoderHandle) {
+ CHECK_EQ(VO_ERR_NONE, mApiHandle->Uninit(mEncoderHandle));
+ mEncoderHandle = NULL;
+ }
+
+ delete mApiHandle;
+ mApiHandle = NULL;
+
+ delete mMemOperator;
+ mMemOperator = NULL;
+}
+
+void SoftAACEncoder::initPorts() {
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ InitOMXParams(&def);
+
+ def.nPortIndex = 0;
+ def.eDir = OMX_DirInput;
+ def.nBufferCountMin = kNumBuffers;
+ def.nBufferCountActual = def.nBufferCountMin;
+ def.nBufferSize = kNumSamplesPerFrame * sizeof(int16_t) * 2;
+ def.bEnabled = OMX_TRUE;
+ def.bPopulated = OMX_FALSE;
+ def.eDomain = OMX_PortDomainAudio;
+ def.bBuffersContiguous = OMX_FALSE;
+ def.nBufferAlignment = 1;
+
+ def.format.audio.cMIMEType = const_cast<char *>("audio/raw");
+ def.format.audio.pNativeRender = NULL;
+ def.format.audio.bFlagErrorConcealment = OMX_FALSE;
+ def.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
+
+ addPort(def);
+
+ def.nPortIndex = 1;
+ def.eDir = OMX_DirOutput;
+ def.nBufferCountMin = kNumBuffers;
+ def.nBufferCountActual = def.nBufferCountMin;
+ def.nBufferSize = 8192;
+ def.bEnabled = OMX_TRUE;
+ def.bPopulated = OMX_FALSE;
+ def.eDomain = OMX_PortDomainAudio;
+ def.bBuffersContiguous = OMX_FALSE;
+ def.nBufferAlignment = 2;
+
+ def.format.audio.cMIMEType = const_cast<char *>("audio/aac");
+ def.format.audio.pNativeRender = NULL;
+ def.format.audio.bFlagErrorConcealment = OMX_FALSE;
+ def.format.audio.eEncoding = OMX_AUDIO_CodingAAC;
+
+ addPort(def);
+}
+
+status_t SoftAACEncoder::initEncoder() {
+ mApiHandle = new VO_AUDIO_CODECAPI;
+
+ if (VO_ERR_NONE != voGetAACEncAPI(mApiHandle)) {
+ ALOGE("Failed to get api handle");
+ return UNKNOWN_ERROR;
+ }
+
+ mMemOperator = new VO_MEM_OPERATOR;
+ mMemOperator->Alloc = cmnMemAlloc;
+ mMemOperator->Copy = cmnMemCopy;
+ mMemOperator->Free = cmnMemFree;
+ mMemOperator->Set = cmnMemSet;
+ mMemOperator->Check = cmnMemCheck;
+
+ VO_CODEC_INIT_USERDATA userData;
+ memset(&userData, 0, sizeof(userData));
+ userData.memflag = VO_IMF_USERMEMOPERATOR;
+ userData.memData = (VO_PTR) mMemOperator;
+ if (VO_ERR_NONE !=
+ mApiHandle->Init(&mEncoderHandle, VO_AUDIO_CodingAAC, &userData)) {
+ ALOGE("Failed to init AAC encoder");
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+OMX_ERRORTYPE SoftAACEncoder::internalGetParameter(
+ OMX_INDEXTYPE index, OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexParamAudioPortFormat:
+ {
+ OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams =
+ (OMX_AUDIO_PARAM_PORTFORMATTYPE *)params;
+
+ if (formatParams->nPortIndex > 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (formatParams->nIndex > 0) {
+ return OMX_ErrorNoMore;
+ }
+
+ formatParams->eEncoding =
+ (formatParams->nPortIndex == 0)
+ ? OMX_AUDIO_CodingPCM : OMX_AUDIO_CodingAAC;
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioAac:
+ {
+ OMX_AUDIO_PARAM_AACPROFILETYPE *aacParams =
+ (OMX_AUDIO_PARAM_AACPROFILETYPE *)params;
+
+ if (aacParams->nPortIndex != 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ aacParams->nBitRate = mBitRate;
+ aacParams->nAudioBandWidth = 0;
+ aacParams->nAACtools = 0;
+ aacParams->nAACERtools = 0;
+ aacParams->eAACProfile = OMX_AUDIO_AACObjectMain;
+ aacParams->eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4FF;
+ aacParams->eChannelMode = OMX_AUDIO_ChannelModeStereo;
+
+ aacParams->nChannels = mNumChannels;
+ aacParams->nSampleRate = mSampleRate;
+ aacParams->nFrameLength = 0;
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPcm:
+ {
+ OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams =
+ (OMX_AUDIO_PARAM_PCMMODETYPE *)params;
+
+ if (pcmParams->nPortIndex != 0) {
+ return OMX_ErrorUndefined;
+ }
+
+ pcmParams->eNumData = OMX_NumericalDataSigned;
+ pcmParams->eEndian = OMX_EndianBig;
+ pcmParams->bInterleaved = OMX_TRUE;
+ pcmParams->nBitPerSample = 16;
+ pcmParams->ePCMMode = OMX_AUDIO_PCMModeLinear;
+ pcmParams->eChannelMapping[0] = OMX_AUDIO_ChannelLF;
+ pcmParams->eChannelMapping[1] = OMX_AUDIO_ChannelRF;
+
+ pcmParams->nChannels = mNumChannels;
+ pcmParams->nSamplingRate = mSampleRate;
+
+ return OMX_ErrorNone;
+ }
+
+ default:
+ return SimpleSoftOMXComponent::internalGetParameter(index, params);
+ }
+}
+
+OMX_ERRORTYPE SoftAACEncoder::internalSetParameter(
+ OMX_INDEXTYPE index, const OMX_PTR params) {
+ switch (index) {
+ case OMX_IndexParamStandardComponentRole:
+ {
+ const OMX_PARAM_COMPONENTROLETYPE *roleParams =
+ (const OMX_PARAM_COMPONENTROLETYPE *)params;
+
+ if (strncmp((const char *)roleParams->cRole,
+ "audio_encoder.aac",
+ OMX_MAX_STRINGNAME_SIZE - 1)) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPortFormat:
+ {
+ const OMX_AUDIO_PARAM_PORTFORMATTYPE *formatParams =
+ (const OMX_AUDIO_PARAM_PORTFORMATTYPE *)params;
+
+ if (formatParams->nPortIndex > 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ if (formatParams->nIndex > 0) {
+ return OMX_ErrorNoMore;
+ }
+
+ if ((formatParams->nPortIndex == 0
+ && formatParams->eEncoding != OMX_AUDIO_CodingPCM)
+ || (formatParams->nPortIndex == 1
+ && formatParams->eEncoding != OMX_AUDIO_CodingAAC)) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioAac:
+ {
+ OMX_AUDIO_PARAM_AACPROFILETYPE *aacParams =
+ (OMX_AUDIO_PARAM_AACPROFILETYPE *)params;
+
+ if (aacParams->nPortIndex != 1) {
+ return OMX_ErrorUndefined;
+ }
+
+ mBitRate = aacParams->nBitRate;
+ mNumChannels = aacParams->nChannels;
+ mSampleRate = aacParams->nSampleRate;
+
+ if (setAudioParams() != OK) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+ case OMX_IndexParamAudioPcm:
+ {
+ OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams =
+ (OMX_AUDIO_PARAM_PCMMODETYPE *)params;
+
+ if (pcmParams->nPortIndex != 0) {
+ return OMX_ErrorUndefined;
+ }
+
+ mNumChannels = pcmParams->nChannels;
+ mSampleRate = pcmParams->nSamplingRate;
+
+ if (setAudioParams() != OK) {
+ return OMX_ErrorUndefined;
+ }
+
+ return OMX_ErrorNone;
+ }
+
+
+ default:
+ return SimpleSoftOMXComponent::internalSetParameter(index, params);
+ }
+}
+
+status_t SoftAACEncoder::setAudioParams() {
+ // We call this whenever sample rate, number of channels or bitrate change
+ // in reponse to setParameter calls.
+
+ ALOGV("setAudioParams: %lu Hz, %lu channels, %lu bps",
+ mSampleRate, mNumChannels, mBitRate);
+
+ status_t err = setAudioSpecificConfigData();
+
+ if (err != OK) {
+ return err;
+ }
+
+ AACENC_PARAM params;
+ memset(¶ms, 0, sizeof(params));
+ params.sampleRate = mSampleRate;
+ params.bitRate = mBitRate;
+ params.nChannels = mNumChannels;
+ params.adtsUsed = 0; // We add adts header in the file writer if needed.
+ if (VO_ERR_NONE != mApiHandle->SetParam(
+ mEncoderHandle, VO_PID_AAC_ENCPARAM, ¶ms)) {
+ ALOGE("Failed to set AAC encoder parameters");
+ return UNKNOWN_ERROR;
+ }
+
+ return OK;
+}
+
+static status_t getSampleRateTableIndex(int32_t sampleRate, int32_t &index) {
+ static const int32_t kSampleRateTable[] = {
+ 96000, 88200, 64000, 48000, 44100, 32000,
+ 24000, 22050, 16000, 12000, 11025, 8000
+ };
+ const int32_t tableSize =
+ sizeof(kSampleRateTable) / sizeof(kSampleRateTable[0]);
+
+ for (int32_t i = 0; i < tableSize; ++i) {
+ if (sampleRate == kSampleRateTable[i]) {
+ index = i;
+ return OK;
+ }
+ }
+
+ return UNKNOWN_ERROR;
+}
+
+status_t SoftAACEncoder::setAudioSpecificConfigData() {
+ // The AAC encoder's audio specific config really only encodes
+ // number of channels and the sample rate (mapped to an index into
+ // a fixed sample rate table).
+
+ int32_t index;
+ status_t err = getSampleRateTableIndex(mSampleRate, index);
+ if (err != OK) {
+ ALOGE("Unsupported sample rate (%lu Hz)", mSampleRate);
+ return err;
+ }
+
+ if (mNumChannels > 2 || mNumChannels <= 0) {
+ ALOGE("Unsupported number of channels(%lu)", mNumChannels);
+ return UNKNOWN_ERROR;
+ }
+
+ // OMX_AUDIO_AACObjectLC
+ mAudioSpecificConfigData[0] = ((0x02 << 3) | (index >> 1));
+ mAudioSpecificConfigData[1] = ((index & 0x01) << 7) | (mNumChannels << 3);
+
+ return OK;
+}
+
+void SoftAACEncoder::onQueueFilled(OMX_U32 portIndex) {
+ if (mSignalledError) {
+ return;
+ }
+
+ List<BufferInfo *> &inQueue = getPortQueue(0);
+ List<BufferInfo *> &outQueue = getPortQueue(1);
+
+ if (!mSentCodecSpecificData) {
+ // The very first thing we want to output is the codec specific
+ // data. It does not require any input data but we will need an
+ // output buffer to store it in.
+
+ if (outQueue.empty()) {
+ return;
+ }
+
+ BufferInfo *outInfo = *outQueue.begin();
+ OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
+ outHeader->nFilledLen = sizeof(mAudioSpecificConfigData);
+ outHeader->nFlags = OMX_BUFFERFLAG_CODECCONFIG;
+
+ uint8_t *out = outHeader->pBuffer + outHeader->nOffset;
+ memcpy(out, mAudioSpecificConfigData, sizeof(mAudioSpecificConfigData));
+
+#if 0
+ ALOGI("sending codec specific data.");
+ hexdump(out, sizeof(mAudioSpecificConfigData));
+#endif
+
+ outQueue.erase(outQueue.begin());
+ outInfo->mOwnedByUs = false;
+ notifyFillBufferDone(outHeader);
+
+ mSentCodecSpecificData = true;
+ }
+
+ size_t numBytesPerInputFrame =
+ mNumChannels * kNumSamplesPerFrame * sizeof(int16_t);
+
+ for (;;) {
+ // We do the following until we run out of buffers.
+
+ while (mInputSize < numBytesPerInputFrame) {
+ // As long as there's still input data to be read we
+ // will drain "kNumSamplesPerFrame * mNumChannels" samples
+ // into the "mInputFrame" buffer and then encode those
+ // as a unit into an output buffer.
+
+ if (mSawInputEOS || inQueue.empty()) {
+ return;
+ }
+
+ BufferInfo *inInfo = *inQueue.begin();
+ OMX_BUFFERHEADERTYPE *inHeader = inInfo->mHeader;
+
+ const void *inData = inHeader->pBuffer + inHeader->nOffset;
+
+ size_t copy = numBytesPerInputFrame - mInputSize;
+ if (copy > inHeader->nFilledLen) {
+ copy = inHeader->nFilledLen;
+ }
+
+ if (mInputFrame == NULL) {
+ mInputFrame = new int16_t[kNumSamplesPerFrame * mNumChannels];
+ }
+
+ if (mInputSize == 0) {
+ mInputTimeUs = inHeader->nTimeStamp;
+ }
+
+ memcpy((uint8_t *)mInputFrame + mInputSize, inData, copy);
+ mInputSize += copy;
+
+ inHeader->nOffset += copy;
+ inHeader->nFilledLen -= copy;
+
+ // "Time" on the input buffer has in effect advanced by the
+ // number of audio frames we just advanced nOffset by.
+ inHeader->nTimeStamp +=
+ (copy * 1000000ll / mSampleRate)
+ / (mNumChannels * sizeof(int16_t));
+
+ if (inHeader->nFilledLen == 0) {
+ if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
+ ALOGV("saw input EOS");
+ mSawInputEOS = true;
+
+ // Pad any remaining data with zeroes.
+ memset((uint8_t *)mInputFrame + mInputSize,
+ 0,
+ numBytesPerInputFrame - mInputSize);
+
+ mInputSize = numBytesPerInputFrame;
+ }
+
+ inQueue.erase(inQueue.begin());
+ inInfo->mOwnedByUs = false;
+ notifyEmptyBufferDone(inHeader);
+
+ inData = NULL;
+ inHeader = NULL;
+ inInfo = NULL;
+ }
+ }
+
+ // At this point we have all the input data necessary to encode
+ // a single frame, all we need is an output buffer to store the result
+ // in.
+
+ if (outQueue.empty()) {
+ return;
+ }
+
+ BufferInfo *outInfo = *outQueue.begin();
+ OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
+
+ VO_CODECBUFFER inputData;
+ memset(&inputData, 0, sizeof(inputData));
+ inputData.Buffer = (unsigned char *)mInputFrame;
+ inputData.Length = numBytesPerInputFrame;
+ CHECK(VO_ERR_NONE ==
+ mApiHandle->SetInputData(mEncoderHandle, &inputData));
+
+ VO_CODECBUFFER outputData;
+ memset(&outputData, 0, sizeof(outputData));
+ VO_AUDIO_OUTPUTINFO outputInfo;
+ memset(&outputInfo, 0, sizeof(outputInfo));
+
+ uint8_t *outPtr = (uint8_t *)outHeader->pBuffer + outHeader->nOffset;
+ size_t outAvailable = outHeader->nAllocLen - outHeader->nOffset;
+
+ VO_U32 ret = VO_ERR_NONE;
+ size_t nOutputBytes = 0;
+ do {
+ outputData.Buffer = outPtr;
+ outputData.Length = outAvailable - nOutputBytes;
+ ret = mApiHandle->GetOutputData(
+ mEncoderHandle, &outputData, &outputInfo);
+ if (ret == VO_ERR_NONE) {
+ outPtr += outputData.Length;
+ nOutputBytes += outputData.Length;
+ }
+ } while (ret != VO_ERR_INPUT_BUFFER_SMALL);
+
+ outHeader->nFilledLen = nOutputBytes;
+
+ outHeader->nFlags = OMX_BUFFERFLAG_ENDOFFRAME;
+
+ if (mSawInputEOS) {
+ // We also tag this output buffer with EOS if it corresponds
+ // to the final input buffer.
+ outHeader->nFlags = OMX_BUFFERFLAG_EOS;
+ }
+
+ outHeader->nTimeStamp = mInputTimeUs;
+
+#if 0
+ ALOGI("sending %d bytes of data (time = %lld us, flags = 0x%08lx)",
+ nOutputBytes, mInputTimeUs, outHeader->nFlags);
+
+ hexdump(outHeader->pBuffer + outHeader->nOffset, outHeader->nFilledLen);
+#endif
+
+ outQueue.erase(outQueue.begin());
+ outInfo->mOwnedByUs = false;
+ notifyFillBufferDone(outHeader);
+
+ outHeader = NULL;
+ outInfo = NULL;
+
+ mInputSize = 0;
+ }
+}
+
+} // namespace android
+
+android::SoftOMXComponent *createSoftOMXComponent(
+ const char *name, const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData, OMX_COMPONENTTYPE **component) {
+ return new android::SoftAACEncoder(name, callbacks, appData, component);
+}
diff --git a/media/libstagefright/codecs/aacenc/SoftAACEncoder.h b/media/libstagefright/codecs/aacenc/SoftAACEncoder.h
new file mode 100644
index 0000000..d148eb7
--- /dev/null
+++ b/media/libstagefright/codecs/aacenc/SoftAACEncoder.h
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+#ifndef SOFT_AAC_ENCODER_H_
+
+#define SOFT_AAC_ENCODER_H_
+
+#include "SimpleSoftOMXComponent.h"
+
+struct VO_AUDIO_CODECAPI;
+struct VO_MEM_OPERATOR;
+
+namespace android {
+
+struct SoftAACEncoder : public SimpleSoftOMXComponent {
+ SoftAACEncoder(
+ const char *name,
+ const OMX_CALLBACKTYPE *callbacks,
+ OMX_PTR appData,
+ OMX_COMPONENTTYPE **component);
+
+protected:
+ virtual ~SoftAACEncoder();
+
+ virtual OMX_ERRORTYPE internalGetParameter(
+ OMX_INDEXTYPE index, OMX_PTR params);
+
+ virtual OMX_ERRORTYPE internalSetParameter(
+ OMX_INDEXTYPE index, const OMX_PTR params);
+
+ virtual void onQueueFilled(OMX_U32 portIndex);
+
+private:
+ enum {
+ kNumBuffers = 4,
+ kNumSamplesPerFrame = 1024,
+ };
+
+ void *mEncoderHandle;
+ VO_AUDIO_CODECAPI *mApiHandle;
+ VO_MEM_OPERATOR *mMemOperator;
+
+ OMX_U32 mNumChannels;
+ OMX_U32 mSampleRate;
+ OMX_U32 mBitRate;
+
+ bool mSentCodecSpecificData;
+ size_t mInputSize;
+ int16_t *mInputFrame;
+ int64_t mInputTimeUs;
+
+ bool mSawInputEOS;
+
+ uint8_t mAudioSpecificConfigData[2];
+
+ bool mSignalledError;
+
+ void initPorts();
+ status_t initEncoder();
+
+ status_t setAudioSpecificConfigData();
+ status_t setAudioParams();
+
+ DISALLOW_EVIL_CONSTRUCTORS(SoftAACEncoder);
+};
+
+} // namespace android
+
+#endif // SOFT_AAC_ENCODER_H_
diff --git a/media/libstagefright/codecs/aacenc/basic_op/oper_32b.c b/media/libstagefright/codecs/aacenc/basic_op/oper_32b.c
index e48af9d..982f4fd 100644
--- a/media/libstagefright/codecs/aacenc/basic_op/oper_32b.c
+++ b/media/libstagefright/codecs/aacenc/basic_op/oper_32b.c
@@ -358,4 +358,4 @@
res = pow2Table[(POW2_TABLE_SIZE*fPart)/y] >> iPart;
return(res);
-}
\ No newline at end of file
+}
diff --git a/media/libstagefright/codecs/aacenc/basic_op/typedefs.h b/media/libstagefright/codecs/aacenc/basic_op/typedefs.h
index c924e2c..2d5d956 100644
--- a/media/libstagefright/codecs/aacenc/basic_op/typedefs.h
+++ b/media/libstagefright/codecs/aacenc/basic_op/typedefs.h
@@ -77,12 +77,12 @@
/*
********* define 32 bit signed/unsigned types & constants
*/
-typedef long Word32;
-typedef unsigned long UWord32;
+typedef int Word32;
+typedef unsigned int UWord32;
-#ifdef LINUX
+#ifndef _MSC_VER
typedef long long Word64;
typedef unsigned long long UWord64;
#else
diff --git a/media/libstagefright/codecs/aacenc/inc/config.h b/media/libstagefright/codecs/aacenc/inc/config.h
index 3b29cef..b0b4c26 100644
--- a/media/libstagefright/codecs/aacenc/inc/config.h
+++ b/media/libstagefright/codecs/aacenc/inc/config.h
@@ -33,4 +33,4 @@
#define MINBITS_COEF 744
-#endif
\ No newline at end of file
+#endif
diff --git a/media/libstagefright/codecs/aacenc/inc/sf_estim.h b/media/libstagefright/codecs/aacenc/inc/sf_estim.h
index 9039f25..997eba5 100644
--- a/media/libstagefright/codecs/aacenc/inc/sf_estim.h
+++ b/media/libstagefright/codecs/aacenc/inc/sf_estim.h
@@ -43,4 +43,4 @@
Word16 logSfbFormFactor[MAX_CHANNELS][MAX_GROUPED_SFB],
Word16 sfbNRelevantLines[MAX_CHANNELS][MAX_GROUPED_SFB],
const Word16 nChannels);
-#endif
\ No newline at end of file
+#endif
diff --git a/media/libstagefright/codecs/aacenc/inc/transform.h b/media/libstagefright/codecs/aacenc/inc/transform.h
index fbac7aa..311cef5 100644
--- a/media/libstagefright/codecs/aacenc/inc/transform.h
+++ b/media/libstagefright/codecs/aacenc/inc/transform.h
@@ -33,4 +33,4 @@
Word16 windowSequence
);
-#endif
\ No newline at end of file
+#endif
diff --git a/media/libstagefright/codecs/aacenc/src/aac_rom.c b/media/libstagefright/codecs/aacenc/src/aac_rom.c
index 08792e8..127322d 100644
--- a/media/libstagefright/codecs/aacenc/src/aac_rom.c
+++ b/media/libstagefright/codecs/aacenc/src/aac_rom.c
@@ -2360,4 +2360,4 @@
0x4d, 0x59, 0x4f, 0x79, 0x53, 0x65, 0x57, 0x75, 0x5b, 0x6d, 0x5f, 0x7d, 0x67, 0x73, 0x6f, 0x7b,
0x00, 0x08, 0x14, 0x1c, 0x22, 0x2a, 0x36, 0x3e, 0x41, 0x49, 0x55, 0x5d, 0x63, 0x6b, 0x77, 0x7f,
0x00,
-};
\ No newline at end of file
+};
diff --git a/media/libstagefright/codecs/aacenc/src/aacenc.c b/media/libstagefright/codecs/aacenc/src/aacenc.c
index b5e8a9c..ad2f29a 100644
--- a/media/libstagefright/codecs/aacenc/src/aacenc.c
+++ b/media/libstagefright/codecs/aacenc/src/aacenc.c
@@ -492,4 +492,4 @@
pDecHandle->Uninit = voAACEncUninit;
return VO_ERR_NONE;
-}
\ No newline at end of file
+}
diff --git a/media/libstagefright/codecs/aacenc/src/adj_thr.c b/media/libstagefright/codecs/aacenc/src/adj_thr.c
index c656f65..a8ab809 100644
--- a/media/libstagefright/codecs/aacenc/src/adj_thr.c
+++ b/media/libstagefright/codecs/aacenc/src/adj_thr.c
@@ -1039,7 +1039,7 @@
/* minSnr adaptation */
/* maximum reduction of minSnr goes down to minSnr^maxRed */
- msaParam->maxRed = 0x20000000; /* *0.25f /
+ msaParam->maxRed = 0x20000000; /* *0.25f */
/* start adaptation of minSnr for avgEn/sfbEn > startRatio */
msaParam->startRatio = 0x0ccccccd; /* 10 */
/* maximum minSnr reduction to minSnr^maxRed is reached for
diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/PrePostMDCT_v5.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/PrePostMDCT_v5.s
index 103cc91..da21d5f 100644
--- a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/PrePostMDCT_v5.s
+++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/PrePostMDCT_v5.s
@@ -128,4 +128,4 @@
PostMDCT_END:
ldmia sp!, {r4 - r11, pc}
@ENDP @ |PostMDCT|
- .end
\ No newline at end of file
+ .end
diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/R4R8First_v5.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/R4R8First_v5.s
index 72cb9a3..4ca4f31 100644
--- a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/R4R8First_v5.s
+++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/R4R8First_v5.s
@@ -249,4 +249,4 @@
.word 0x5a82799a
@ENDP @ |Radix8First|
- .end
\ No newline at end of file
+ .end
diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/Radix4FFT_v5.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/Radix4FFT_v5.s
index e81c82e..b59b967 100644
--- a/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/Radix4FFT_v5.s
+++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV5E/Radix4FFT_v5.s
@@ -166,4 +166,4 @@
ldmia sp!, {r4 - r11, pc}
@ENDP @ |Radix4FFT|
- .end
\ No newline at end of file
+ .end
diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/PrePostMDCT_v7.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/PrePostMDCT_v7.s
index 64d767a..7f6b881 100644
--- a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/PrePostMDCT_v7.s
+++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/PrePostMDCT_v7.s
@@ -23,9 +23,13 @@
.section .text
.global PreMDCT
+ .fnstart
PreMDCT:
stmdb sp!, {r4 - r11, lr}
+ .save {r4 - r11, lr}
+ fstmfdd sp!, {d8 - d15}
+ .vsave {d8 - d15}
add r9, r0, r1, lsl #2
sub r3, r9, #32
@@ -74,14 +78,20 @@
bne PreMDCT_LOOP
PreMDCT_END:
+ fldmfdd sp!, {d8 - d15}
ldmia sp!, {r4 - r11, pc}
@ENDP @ |PreMDCT|
+ .fnend
.section .text
.global PostMDCT
+ .fnstart
PostMDCT:
stmdb sp!, {r4 - r11, lr}
+ .save {r4 - r11, lr}
+ fstmfdd sp!, {d8 - d15}
+ .vsave {d8 - d15}
add r9, r0, r1, lsl #2
sub r3, r9, #32
@@ -129,7 +139,8 @@
bne PostMDCT_LOOP
PostMDCT_END:
+ fldmfdd sp!, {d8 - d15}
ldmia sp!, {r4 - r11, pc}
@ENDP @ |PostMDCT|
- .end
\ No newline at end of file
+ .fnend
diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/R4R8First_v7.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/R4R8First_v7.s
index 7fc5520..03fa6a9 100644
--- a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/R4R8First_v7.s
+++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/R4R8First_v7.s
@@ -23,9 +23,13 @@
.section .text
.global Radix8First
+ .fnstart
Radix8First:
stmdb sp!, {r4 - r11, lr}
+ .save {r4 - r11, lr}
+ fstmfdd sp!, {d8 - d15}
+ .vsave {d8 - d15}
ldr r3, SQRT1_2
cmp r1, #0
@@ -103,17 +107,23 @@
bne Radix8First_LOOP
Radix8First_END:
+ fldmfdd sp!, {d8 - d15}
ldmia sp!, {r4 - r11, pc}
SQRT1_2:
.word 0x2d413ccd
@ENDP @ |Radix8First|
+ .fnend
.section .text
.global Radix4First
+ .fnstart
Radix4First:
stmdb sp!, {r4 - r11, lr}
+ .save {r4 - r11, lr}
+ fstmfdd sp!, {d8 - d15}
+ .vsave {d8 - d15}
cmp r1, #0
beq Radix4First_END
@@ -140,7 +150,8 @@
bne Radix4First_LOOP
Radix4First_END:
+ fldmfdd sp!, {d8 - d15}
ldmia sp!, {r4 - r11, pc}
@ENDP @ |Radix4First|
- .end
\ No newline at end of file
+ .fnend
diff --git a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/Radix4FFT_v7.s b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/Radix4FFT_v7.s
index b8655ae..431bc30 100644
--- a/media/libstagefright/codecs/aacenc/src/asm/ARMV7/Radix4FFT_v7.s
+++ b/media/libstagefright/codecs/aacenc/src/asm/ARMV7/Radix4FFT_v7.s
@@ -23,9 +23,13 @@
.section .text
.global Radix4FFT
+ .fnstart
Radix4FFT:
stmdb sp!, {r4 - r11, lr}
+ .save {r4 - r11, lr}
+ fstmfdd sp!, {d8 - d15}
+ .vsave {d8 - d15}
mov r1, r1, asr #2
cmp r1, #0
@@ -137,7 +141,8 @@
bne Radix4FFT_LOOP1
Radix4FFT_END:
+ fldmfdd sp!, {d8 - d15}
ldmia sp!, {r4 - r11, pc}
@ENDP @ |Radix4FFT|
- .end
\ No newline at end of file
+ .fnend
diff --git a/media/libstagefright/codecs/aacenc/src/band_nrg.c b/media/libstagefright/codecs/aacenc/src/band_nrg.c
index 7501af1..e4034b8 100644
--- a/media/libstagefright/codecs/aacenc/src/band_nrg.c
+++ b/media/libstagefright/codecs/aacenc/src/band_nrg.c
@@ -99,4 +99,4 @@
*bandEnergySideSum = accuSideSum;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/media/libstagefright/codecs/amrwbenc/src/mem_align.c b/media/libstagefright/codecs/amrwbenc/src/mem_align.c
index a29baf3..3b7853f 100644
--- a/media/libstagefright/codecs/amrwbenc/src/mem_align.c
+++ b/media/libstagefright/codecs/amrwbenc/src/mem_align.c
@@ -23,6 +23,11 @@
#include "mem_align.h"
+#ifdef _MSC_VER
+#include <stddef.h>
+#else
+#include <stdint.h>
+#endif
/*****************************************************************************
*
@@ -66,8 +71,8 @@
pMemop->Set(CodecID, tmp, 0, size + alignment);
mem_ptr =
- (unsigned char *) ((unsigned int) (tmp + alignment - 1) &
- (~((unsigned int) (alignment - 1))));
+ (unsigned char *) ((intptr_t) (tmp + alignment - 1) &
+ (~((intptr_t) (alignment - 1))));
if (mem_ptr == tmp)
mem_ptr += alignment;
diff --git a/media/libstagefright/omx/SoftOMXPlugin.cpp b/media/libstagefright/omx/SoftOMXPlugin.cpp
index da3ae42..cf9e8c9 100644
--- a/media/libstagefright/omx/SoftOMXPlugin.cpp
+++ b/media/libstagefright/omx/SoftOMXPlugin.cpp
@@ -35,6 +35,7 @@
} kComponents[] = {
{ "OMX.google.aac.decoder", "aacdec", "audio_decoder.aac" },
+ { "OMX.google.aac.encoder", "aacenc", "audio_encoder.aac" },
{ "OMX.google.amrnb.decoder", "amrdec", "audio_decoder.amrnb" },
{ "OMX.google.amrwb.decoder", "amrdec", "audio_decoder.amrwb" },
{ "OMX.google.h264.decoder", "h264dec", "video_decoder.avc" },
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioManagerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioManagerTest.java
index c9087d1..7967ce7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioManagerTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/audio/MediaAudioManagerTest.java
@@ -19,8 +19,13 @@
import com.android.mediaframeworktest.MediaFrameworkTest;
import android.content.Context;
import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
/**
* Junit / Instrumentation test case for the media AudioManager api
@@ -28,8 +33,13 @@
public class MediaAudioManagerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
- private String TAG = "MediaAudioManagerTest";
+ private final static String TAG = "MediaAudioManagerTest";
+ // the AudioManager used throughout the test
private AudioManager mAudioManager;
+ // keep track of looper for AudioManager so we can terminate it
+ private Looper mAudioManagerLooper;
+ private final Object mLooperLock = new Object();
+ private final static int WAIT_FOR_LOOPER_TO_INITIALIZE_MS = 60000; // 60s
private int[] ringtoneMode = {AudioManager.RINGER_MODE_NORMAL,
AudioManager.RINGER_MODE_SILENT, AudioManager.RINGER_MODE_VIBRATE};
@@ -37,17 +47,48 @@
super("com.android.mediaframeworktest", MediaFrameworkTest.class);
}
+ private void initializeAudioManagerWithLooper() {
+ new Thread() {
+ @Override
+ public void run() {
+ Looper.prepare();
+ mAudioManagerLooper = Looper.myLooper();
+ mAudioManager = (AudioManager)getActivity().getSystemService(Context.AUDIO_SERVICE);
+ synchronized (mLooperLock) {
+ mLooperLock.notify();
+ }
+ Looper.loop();
+ }
+ }.start();
+ }
+
@Override
protected void setUp() throws Exception {
super.setUp();
- mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+ synchronized(mLooperLock) {
+ initializeAudioManagerWithLooper();
+ try {
+ mLooperLock.wait(WAIT_FOR_LOOPER_TO_INITIALIZE_MS);
+ } catch (Exception e) {
+ assertTrue("initializeAudioManagerWithLooper() failed to complete in time", false);
+ }
+ }
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
+ synchronized(mLooperLock) {
+ if (mAudioManagerLooper != null) {
+ mAudioManagerLooper.quit();
+ }
+ }
}
+ //-----------------------------------------------------------------
+ // Ringer Mode
+ //----------------------------------
+
public boolean validateSetRingTone(int i) {
int getRingtone = mAudioManager.getRingerMode();
if (i != getRingtone)
@@ -67,4 +108,136 @@
assertTrue("SetRingtoneMode : " + ringtoneMode[i], result);
}
}
+
+ //-----------------------------------------------------------------
+ // AudioFocus
+ //----------------------------------
+
+ private static AudioFocusListener mAudioFocusListener;
+ private final static int INVALID_FOCUS = -80; // initialized to magic invalid focus change type
+ private final static int WAIT_FOR_AUDIOFOCUS_LOSS_MS = 10;
+
+ private static class AudioFocusListener implements OnAudioFocusChangeListener {
+ public int mLastFocusChange = INVALID_FOCUS;
+ public int mFocusChangeCounter = 0;
+ public AudioFocusListener() {
+ }
+ public void onAudioFocusChange(int focusChange) {
+ mLastFocusChange = focusChange;
+ mFocusChangeCounter++;
+ }
+ }
+
+ /**
+ * Fails the test if expectedFocusLossMode != mAudioFocusListener.mLastFocusChange
+ */
+ private void verifyAudioFocusLoss(int focusGainMode, int expectedFocusLossMode)
+ throws Exception {
+ // request AudioFocus so we can test that mAudioFocusListener loses it when another
+ // request comes in
+ int result = mAudioManager.requestAudioFocus(mAudioFocusListener,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ assertTrue("requestAudioFocus returned " + result,
+ result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ // cause mAudioFocusListener to lose AudioFocus
+ result = mAudioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
+ focusGainMode);
+ assertTrue("requestAudioFocus returned " + result,
+ result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ // the audio focus request is async, so wait a bit to verify it had the expected effect
+ java.lang.Thread.sleep(WAIT_FOR_AUDIOFOCUS_LOSS_MS);
+ // test successful if the expected focus loss was recorded
+ assertEquals("listener lost focus",
+ mAudioFocusListener.mLastFocusChange, expectedFocusLossMode);
+ }
+
+ private void setupAudioFocusListener() {
+ mAudioFocusListener = new AudioFocusListener();
+ mAudioManager.registerAudioFocusListener(mAudioFocusListener);
+ }
+
+ private void cleanupAudioFocusListener() {
+ // clean up
+ mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ mAudioManager.unregisterAudioFocusListener(mAudioFocusListener);
+ }
+
+ //----------------------------------
+
+ //Test case 1: test audio focus listener loses audio focus:
+ // AUDIOFOCUS_GAIN causes AUDIOFOCUS_LOSS
+ @MediumTest
+ public void testAudioFocusLoss() throws Exception {
+ setupAudioFocusListener();
+
+ verifyAudioFocusLoss(AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_LOSS);
+
+ cleanupAudioFocusListener();
+ }
+
+ //Test case 2: test audio focus listener loses audio focus:
+ // AUDIOFOCUS_GAIN_TRANSIENT causes AUDIOFOCUS_LOSS_TRANSIENT
+ @MediumTest
+ public void testAudioFocusLossTransient() throws Exception {
+ setupAudioFocusListener();
+
+ verifyAudioFocusLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
+
+ cleanupAudioFocusListener();
+ }
+
+ //Test case 3: test audio focus listener loses audio focus:
+ // AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK causes AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
+ @MediumTest
+ public void testAudioFocusLossTransientDuck() throws Exception {
+ setupAudioFocusListener();
+
+ verifyAudioFocusLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK);
+
+ cleanupAudioFocusListener();
+ }
+
+ //Test case 4: test audio focus registering and use over 3000 iterations
+ @LargeTest
+ public void testAudioFocusStressListenerRequestAbandon() throws Exception {
+ final int ITERATIONS = 3000;
+ // here we only test the life cycle of a focus listener, and make sure we don't crash
+ // when doing it many times without waiting
+ for (int i = 0 ; i < ITERATIONS ; i++) {
+ setupAudioFocusListener();
+ int result = mAudioManager.requestAudioFocus(mAudioFocusListener,
+ AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+ assertTrue("audio focus request was not granted",
+ result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
+ cleanupAudioFocusListener();
+ }
+ assertTrue("testAudioFocusListenerLifeCycle : tested" + ITERATIONS +" iterations", true);
+ }
+
+ //Test case 5: test audio focus use without listener
+ @LargeTest
+ public void testAudioFocusStressNoListenerRequestAbandon() throws Exception {
+ final int ITERATIONS = 1000;
+ // make sure we have a listener in the stack
+ setupAudioFocusListener();
+ mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ // keep making the current owner lose and gain audio focus repeatedly
+ for (int i = 0 ; i < ITERATIONS ; i++) {
+ mAudioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ mAudioManager.abandonAudioFocus(null);
+ // the audio focus request is async, so wait a bit to verify it had the expected effect
+ java.lang.Thread.sleep(WAIT_FOR_AUDIOFOCUS_LOSS_MS);
+ }
+ // verify there were 2 audio focus changes per iteration (one loss + one gain)
+ assertTrue("testAudioFocusListenerLifeCycle : observed " +
+ mAudioFocusListener.mFocusChangeCounter + " AudioFocus changes",
+ mAudioFocusListener.mFocusChangeCounter == ITERATIONS * 2);
+ mAudioManager.abandonAudioFocus(mAudioFocusListener);
+ mAudioManager.unregisterAudioFocusListener(mAudioFocusListener);
+ }
}
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
index 4d683fb..06cd3da 100644
--- a/native/include/android/configuration.h
+++ b/native/include/android/configuration.h
@@ -42,6 +42,8 @@
ACONFIGURATION_DENSITY_MEDIUM = 160,
ACONFIGURATION_DENSITY_TV = 213,
ACONFIGURATION_DENSITY_HIGH = 240,
+ ACONFIGURATION_DENSITY_XHIGH = 320,
+ ACONFIGURATION_DENSITY_XXHIGH = 480,
ACONFIGURATION_DENSITY_NONE = 0xffff,
ACONFIGURATION_KEYBOARD_ANY = 0x0000,
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index 664f258..8b37da5 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -477,6 +477,26 @@
return result;
}
+void EGLAPI eglBeginFrame(EGLDisplay dpy, EGLSurface surface) {
+ clearError();
+
+ egl_display_t const * const dp = validate_display(dpy);
+ if (!dp) {
+ return;
+ }
+
+ SurfaceRef _s(dp, surface);
+ if (!_s.get()) {
+ setError(EGL_BAD_SURFACE, EGL_FALSE);
+ return;
+ }
+
+ int64_t timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ egl_surface_t const * const s = get_surface(surface);
+ native_window_set_buffers_timestamp(s->win.get(), timestamp);
+}
+
// ----------------------------------------------------------------------------
// Contexts
// ----------------------------------------------------------------------------
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_0.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_0.png
index f24d801..55272f5 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_0.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_0_fully.png
index 66eb5db..e5e6305 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_1.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_1.png
index edff74a..f595ae1 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_1.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_1_fully.png
index 1cdd4eb..f555fc9 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_2.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_2.png
index 95fdaf9..ecf1349 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_2.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_2_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_2_fully.png
index 8678e39..918a9f9 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_3.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_3.png
index 1d2d290..f5d1479 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_3.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png
index c2e4b78..f58a19c 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png
index 51b839f..744b1fa 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_disconnected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_idle.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_idle.png
index b20c5c7..bef4358 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_idle.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_wimax_signal_idle.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
index b0e1424..a2ba6c4 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
index 797b1eb..00b560c 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
index 7c479e8..fd8d2f2 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
index c60def1..92364d2 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
index 1cef87b..3b4aaa1 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
index 61e0e95..8cea4e93f 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
index 48c3490..873a317 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
index 71205bf..94a4a35 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
index e5b0e2d..d2381fcc 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
index 5e74e44..93552cb 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..daf18c7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_0.png
new file mode 100644
index 0000000..a0c7a99
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_0_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_0_fully.png
new file mode 100644
index 0000000..b1f1e5b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_1.png
new file mode 100644
index 0000000..8b31618
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_1_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_1_fully.png
new file mode 100644
index 0000000..1a62682
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_2.png
new file mode 100644
index 0000000..ff51551
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_2_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_2_fully.png
new file mode 100644
index 0000000..0374142
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_3.png
new file mode 100644
index 0000000..8f881f2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png
new file mode 100644
index 0000000..7870cee1
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png
new file mode 100644
index 0000000..65404c2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_disconnected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_idle.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_idle.png
new file mode 100644
index 0000000..327f89d
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_data_wimax_signal_idle.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
index 76b272e..d93a661 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png
index 18c603d..b39cc04 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
index 89274b1..4305351 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png
index ae8e70a..4305be2 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
index 35ec9bd..beb641b 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png
index b082e9f..7b8ddc2 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
index a2c7ed8..a4028cd 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png
index e12ecbf..fad1873 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
index f08b75e..b5ed22b 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png
index 12581d5..cca7bf3 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..5292998
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_0.png
index a290cf0..ac322ba 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_0_fully.png
index 09314e9..ac322ba 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_1.png
index 90de934..f139bbe 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_1_fully.png
index cdcac61..af67018 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_2.png
index 570a9b5..fe404e2 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_2_fully.png
index 68f3075..1ffa9b6 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_3.png
index 07b03fa..75cd8ee 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_3_fully.png
index 9dff62d..666d1f6 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_4.png
index f855c1c..da9607b 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_4_fully.png
index 119ce32..d05297f 100644
--- a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..3733a38
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_0.png
index 926b081..f931c60 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_0_fully.png
index 4498bed..f931c60 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_1.png
index 60b00a8..398f4d5 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_1_fully.png
index 8e9be27..a0fc3f2 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_2.png
index 014d838..5fe96a3 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_2_fully.png
index 1755088..8a66255 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_3.png
index 44e7905..e785a7a 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_3_fully.png
index bfc17dd..63be95d 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_4.png
index e39d7d7..533bcdc 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_4_fully.png
index 466d3b5..566172e 100644
--- a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..ab718ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_0.png
index 1744f65..ccf1ba1 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_0_fully.png
index 6270e51..ccf1ba1 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_1.png
index b47624f..07937fe 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_1_fully.png
index 9fd562d..ba1b077 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_2.png
index 42630a2..a705a89 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_2_fully.png
index 1777ce7..0187d12 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_3.png
index d35d546..0ed7d6f4 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_3_fully.png
index 324ec32..24a6e5a 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_4.png
index 54f3ae7..1a47801 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_4_fully.png
index 290dafa..d9648b6f 100644
--- a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..023bbe6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_0.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_0.png
new file mode 100644
index 0000000..7a8d1f3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_0_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_0_fully.png
new file mode 100644
index 0000000..5f86bbb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_1.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_1.png
new file mode 100644
index 0000000..70e2011
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_1_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_1_fully.png
new file mode 100644
index 0000000..c1d1cc3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_2.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_2.png
new file mode 100644
index 0000000..c62d977
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_2_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_2_fully.png
new file mode 100644
index 0000000..86d30df
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_3.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_3.png
new file mode 100644
index 0000000..b112748
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png
new file mode 100644
index 0000000..bfc7d81
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png
new file mode 100644
index 0000000..bea643f3a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_disconnected.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_idle.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_idle.png
new file mode 100644
index 0000000..a8a89d6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_data_wimax_signal_idle.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_0.png
index 9c80517..0a28885 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_0_fully.png
index b144e18..bbe70cc 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_1.png
index 32762da..9943613 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_1_fully.png
index ae02c8f..e25a55c 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_2.png
index f95f677..1fc1775 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_2_fully.png
index 23343e9..d1aefca 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_3.png
index d72d42b..82b9741 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_3_fully.png
index 3e5eaf4..c8c2c63 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_4.png
index bf8ca9f..9f4979c 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_4_fully.png
index e9de257..b2e64b9 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..3e7fefd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel_title.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel_title.xml
index c9b1673..b985aaf 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel_title.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_notification_panel_title.xml
@@ -44,12 +44,13 @@
<FrameLayout
android:layout_height="wrap_content"
android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
>
<ImageView
android:id="@+id/bluetooth"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:scaleType="centerInside"
+ android:paddingRight="16dp"
android:visibility="gone"
android:contentDescription="@null"
android:layout_gravity="center_vertical"
@@ -61,8 +62,8 @@
android:id="@+id/mobile_icon"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:paddingRight="4dp"
android:layout_gravity="center_vertical"
+ android:paddingRight="6dp"
>
<ImageView
@@ -86,8 +87,7 @@
android:layout_gravity="left|center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingRight="8dp"
- android:layout_weight="1"
+ android:paddingRight="12dp"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/status_bar_settings_settings_button"
@@ -98,8 +98,8 @@
android:id="@+id/wifi_icon"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:paddingRight="4dp"
android:layout_gravity="center_vertical"
+ android:paddingRight="6dp"
>
<ImageView
@@ -123,7 +123,7 @@
android:layout_gravity="left|center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingRight="8dp"
+ android:paddingRight="12dp"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/status_bar_settings_settings_button"
@@ -136,18 +136,17 @@
android:scaleType="centerInside"
android:layout_gravity="center_vertical"
android:layout_alignBaseline="@id/wifi_signal"
- android:paddingLeft="8dp"
- android:paddingRight="8dp"
+ android:paddingRight="6dp"
android:contentDescription="@null"
/>
<TextView
android:id="@+id/battery_text"
style="@style/StatusBarNotificationText"
- android:layout_width="56dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
- android:paddingRight="8dp"
+ android:paddingRight="2dp"
android:singleLine="true"
android:text="@string/status_bar_settings_settings_button"
/>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index a6c64ad..d083467 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -101,7 +101,12 @@
<string name="accessibility_wifi_one_bar" msgid="6854947280074467207">"Wi-Fi, адзiн слупок."</string>
<string name="accessibility_wifi_two_bars" msgid="3344340012058984348">"Wi-Fi, два слупкi."</string>
<string name="accessibility_wifi_three_bars" msgid="928322805193265041">"Wi-Fi, тры слупкi."</string>
- <string name="accessibility_wifi_signal_full" msgid="4826278754383492058">"Моцны сiгнал Wi-Fi."</string>
+ <string name="accessibility_wifi_signal_full" msgid="1275764416228473932">"Поўны сігнал Wi-Fi."</string>
+ <string name="accessibility_no_wimax" msgid="4329180129727630368">"Няма сiгналу WiMAX."</string>
+ <string name="accessibility_wimax_one_bar" msgid="4170994299011863648">"Адзiн слупок сiгналу WiMAX."</string>
+ <string name="accessibility_wimax_two_bars" msgid="9176236858336502288">"Два слупкi сiгналу WiMAX."</string>
+ <string name="accessibility_wimax_three_bars" msgid="6116551636752103927">"Тры слупкi сiгналу WiMAX."</string>
+ <string name="accessibility_wimax_signal_full" msgid="2768089986795579558">"Моцны сiгнал WiMAX."</string>
<string name="accessibility_data_connection_gprs" msgid="1606477224486747751">"GPRS"</string>
<string name="accessibility_data_connection_3g" msgid="8628562305003568260">"3G"</string>
<string name="accessibility_data_connection_3.5g" msgid="8664845609981692001">"3.5G"</string>
@@ -135,4 +140,5 @@
<string name="gps_notification_searching_text" msgid="8574247005642736060">"Пошук GPS"</string>
<string name="gps_notification_found_text" msgid="4619274244146446464">"Месца задана праз GPS"</string>
<string name="accessibility_clear_all" msgid="5235938559247164925">"Выдалiць усе апавяшчэннi."</string>
+ <string name="dreams_dock_launcher" msgid="3541196417659166245">"Актывацыя экраннай застаўкі"</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS-large/strings.xml b/packages/SystemUI/res/values-es-rUS-large/strings.xml
index 3f96e87..dd44b28 100644
--- a/packages/SystemUI/res/values-es-rUS-large/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS-large/strings.xml
@@ -19,7 +19,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="status_bar_clear_all_button" msgid="4661583896803349732">"Borrar todas"</string>
+ <string name="status_bar_clear_all_button" msgid="4661583896803349732">"Eliminar todas"</string>
<string name="notifications_off_title" msgid="1860117696034775851">"Notificaciones desactivadas"</string>
<string name="notifications_off_text" msgid="1439152806320786912">"Toca aquí para volver a activar las notificaciones."</string>
</resources>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 85818ae..a700908 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -20,7 +20,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="7164937344850004466">"IU del sistema"</string>
- <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string>
+ <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Eliminar"</string>
<string name="status_bar_do_not_disturb_button" msgid="5812628897510997853">"No molestar"</string>
<string name="status_bar_please_disturb_button" msgid="3345398298841572813">"Mostrar notificaciones"</string>
<string name="status_bar_recent_remove_item_title" msgid="6026395868129852968">"Eliminar de la lista"</string>
@@ -122,7 +122,7 @@
<skip />
<string name="accessibility_settings_button" msgid="799583911231893380">"Configuración del sistema"</string>
<string name="accessibility_notifications_button" msgid="4498000369779421892">"Notificaciones"</string>
- <string name="accessibility_remove_notification" msgid="3603099514902182350">"Borrar notificación"</string>
+ <string name="accessibility_remove_notification" msgid="3603099514902182350">"Eliminar notificación"</string>
<string name="accessibility_gps_enabled" msgid="3511469499240123019">"GPS habilitado"</string>
<string name="accessibility_gps_acquiring" msgid="8959333351058967158">"Adquisición de GPS"</string>
<string name="accessibility_tty_enabled" msgid="4613200365379426561">"TeleTypewriter habilitado"</string>
@@ -133,12 +133,12 @@
<string name="data_usage_disabled_dialog_4g_title" msgid="4789143363492682629">"Datos de 4G inhabilitados"</string>
<string name="data_usage_disabled_dialog_mobile_title" msgid="1046047248844821202">"Se inhabilitaron los datos móviles"</string>
<string name="data_usage_disabled_dialog_title" msgid="2086815304858964954">"Datos inhabilitados"</string>
- <string name="data_usage_disabled_dialog" msgid="3853117269051806280">"Alcanzaste el límite de uso de datos especificado."\n\n"Puede que tu operador te cobre por volver a habilitar datos."</string>
- <string name="data_usage_disabled_dialog_enable" msgid="7729772039208664606">"Volver a habilitar datos"</string>
+ <string name="data_usage_disabled_dialog" msgid="3853117269051806280">"Alcanzaste el límite de uso de datos especificado."\n\n"Puede que tu operador te cobre por volver a activar datos."</string>
+ <string name="data_usage_disabled_dialog_enable" msgid="7729772039208664606">"Volver a activar datos"</string>
<string name="status_bar_settings_signal_meter_disconnected" msgid="1940231521274147771">"Sin conexión a Internet"</string>
<string name="status_bar_settings_signal_meter_wifi_nossid" msgid="6557486452774597820">"Wi-Fi conectado"</string>
<string name="gps_notification_searching_text" msgid="8574247005642736060">"Buscando GPS"</string>
<string name="gps_notification_found_text" msgid="4619274244146446464">"La ubicación se estableció por GPS"</string>
- <string name="accessibility_clear_all" msgid="5235938559247164925">"Borrar todas las notificaciones"</string>
+ <string name="accessibility_clear_all" msgid="5235938559247164925">"Eliminar todas las notificaciones"</string>
<string name="dreams_dock_launcher" msgid="3541196417659166245">"Activar el protector de pantalla"</string>
</resources>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 943b8ca..a5bfb4a 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -101,7 +101,12 @@
<string name="accessibility_wifi_one_bar" msgid="6854947280074467207">"WiFi signaal: üks post."</string>
<string name="accessibility_wifi_two_bars" msgid="3344340012058984348">"WiFi signaal: kaks posti."</string>
<string name="accessibility_wifi_three_bars" msgid="928322805193265041">"WiFi signaal: kolm posti."</string>
- <string name="accessibility_wifi_signal_full" msgid="4826278754383492058">"WiFi signaal on täis."</string>
+ <string name="accessibility_wifi_signal_full" msgid="1275764416228473932">"WiFi-signaal on tugev."</string>
+ <string name="accessibility_no_wimax" msgid="4329180129727630368">"WiMAX-i pole."</string>
+ <string name="accessibility_wimax_one_bar" msgid="4170994299011863648">"WiMAX-i on üks riba."</string>
+ <string name="accessibility_wimax_two_bars" msgid="9176236858336502288">"WiMAX-i on kaks riba."</string>
+ <string name="accessibility_wimax_three_bars" msgid="6116551636752103927">"WiMAX-i on kolm riba."</string>
+ <string name="accessibility_wimax_signal_full" msgid="2768089986795579558">"WiMAX-i signaal on tugev."</string>
<string name="accessibility_data_connection_gprs" msgid="1606477224486747751">"GPRS"</string>
<string name="accessibility_data_connection_3g" msgid="8628562305003568260">"3G"</string>
<string name="accessibility_data_connection_3.5g" msgid="8664845609981692001">"3,5G"</string>
@@ -133,4 +138,5 @@
<string name="gps_notification_searching_text" msgid="8574247005642736060">"GPS-i otsimine"</string>
<string name="gps_notification_found_text" msgid="4619274244146446464">"GPS-i määratud asukoht"</string>
<string name="accessibility_clear_all" msgid="5235938559247164925">"Kustuta kõik teatised."</string>
+ <string name="dreams_dock_launcher" msgid="3541196417659166245">"Aktiveeri ekraanisäästja"</string>
</resources>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 545d54e..dec4def 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -115,7 +115,7 @@
<string name="accessibility_data_connection_edge" msgid="4477457051631979278">"Edge"</string>
<string name="accessibility_data_connection_wifi" msgid="2324496756590645221">"Wi-Fi"</string>
<string name="accessibility_no_sim" msgid="8274017118472455155">"Sem SIM."</string>
- <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Vínculo Bluetooth."</string>
+ <string name="accessibility_bluetooth_tether" msgid="4102784498140271969">"Tethering Bluetooth."</string>
<string name="accessibility_airplane_mode" msgid="834748999790763092">"Modo de avião."</string>
<!-- String.format failed for translation -->
<!-- no translation found for accessibility_battery_level (7451474187113371965) -->
diff --git a/packages/SystemUI/res/values-rm/strings.xml b/packages/SystemUI/res/values-rm/strings.xml
index 2106b89..983df47 100644
--- a/packages/SystemUI/res/values-rm/strings.xml
+++ b/packages/SystemUI/res/values-rm/strings.xml
@@ -39,8 +39,10 @@
<string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Nagins avis"</string>
<string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Actual"</string>
<string name="status_bar_latest_events_title" msgid="6594767438577593172">"Avis"</string>
- <!-- outdated translation 7923774589611311406 --> <string name="battery_low_title" msgid="2783104807551211639">"Connectar il chargiabattarias"</string>
- <!-- outdated translation 7388781709819722764 --> <string name="battery_low_subtitle" msgid="1752040062087829196">"L\'accu è prest vid."</string>
+ <!-- no translation found for battery_low_title (2783104807551211639) -->
+ <skip />
+ <!-- no translation found for battery_low_subtitle (1752040062087829196) -->
+ <skip />
<!-- no translation found for battery_low_percent_format (1077244949318261761) -->
<skip />
<!-- no translation found for invalid_charger (4549105996740522523) -->
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 17330b1..963976d 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -36,7 +36,7 @@
<string name="status_bar_latest_events_title" msgid="6594767438577593172">"Уведомления"</string>
<string name="battery_low_title" msgid="2783104807551211639">"Подключите зарядное устройство"</string>
<string name="battery_low_subtitle" msgid="1752040062087829196">"Батарея разряжена."</string>
- <string name="battery_low_percent_format" msgid="1077244949318261761">"Осталось: <xliff:g id="NUMBER">%d%%</xliff:g>"</string>
+ <string name="battery_low_percent_format" msgid="1077244949318261761">"Осталось <xliff:g id="NUMBER">%d%%</xliff:g>"</string>
<string name="invalid_charger" msgid="4549105996740522523">"Зарядка через порт USB не поддерживается."\n"Используйте только зарядное устройство из комплекта поставки."</string>
<string name="battery_low_why" msgid="7279169609518386372">"Расход заряда батареи"</string>
<string name="status_bar_settings_settings_button" msgid="3023889916699270224">"Настройки"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 3843ea9..70e29eb 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -26,7 +26,7 @@
<string name="status_bar_recent_remove_item_title" msgid="6026395868129852968">"Ondoa kwenye orodha"</string>
<string name="status_bar_recent_inspect_item_title" msgid="7793624864528818569">"Taarifa za programu-matumizi"</string>
<string name="status_bar_no_recent_apps" msgid="6576392951053994640">"Hakuna programu za sasa"</string>
- <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"Ondosha programu za hivi karibuni"</string>
+ <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"Ondosha prog za hivi karibuni"</string>
<!-- String.format failed for translation -->
<!-- no translation found for status_bar_accessibility_recent_apps:other (1040784359794890744) -->
<string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Hakuna arifa"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
index dcda9c2..4145fc4 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
@@ -68,22 +68,13 @@
// get the icon size we want -- on tablets, we use bigger icons
boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
- int density = res.getDisplayMetrics().densityDpi;
if (isTablet) {
- if (density == DisplayMetrics.DENSITY_LOW) {
- mIconDpi = DisplayMetrics.DENSITY_MEDIUM;
- } else if (density == DisplayMetrics.DENSITY_MEDIUM) {
- mIconDpi = DisplayMetrics.DENSITY_HIGH;
- } else if (density == DisplayMetrics.DENSITY_HIGH) {
- mIconDpi = DisplayMetrics.DENSITY_XHIGH;
- } else if (density == DisplayMetrics.DENSITY_XHIGH) {
- // We'll need to use a denser icon, or some sort of a mipmap
- mIconDpi = DisplayMetrics.DENSITY_XHIGH;
- }
+ ActivityManager activityManager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mIconDpi = activityManager.getLauncherLargeIconDensity();
} else {
mIconDpi = res.getDisplayMetrics().densityDpi;
}
- mIconDpi = isTablet ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
// Render the default thumbnail background
int width = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 69a247d..d33ed3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -23,6 +23,8 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
import android.os.ServiceManager;
import android.util.AttributeSet;
import android.util.Slog;
@@ -66,6 +68,35 @@
int mDisabledFlags = 0;
int mNavigationIconHints = 0;
+ // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
+ final static boolean WORKAROUND_INVALID_LAYOUT = true;
+ final static int MSG_CHECK_INVALID_LAYOUT = 8686;
+
+ private class H extends Handler {
+ public void handleMessage(Message m) {
+ switch (m.what) {
+ case MSG_CHECK_INVALID_LAYOUT:
+ final String how = "" + m.obj;
+ final int w = getWidth();
+ final int h = getHeight();
+ final int vw = mCurrentView.getWidth();
+ final int vh = mCurrentView.getHeight();
+
+ if (h != vh || w != vw) {
+ Slog.w(TAG, String.format(
+ "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)",
+ how, w, h, vw, vh));
+ if (WORKAROUND_INVALID_LAYOUT) {
+ requestLayout();
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private H mHandler = new H();
+
public View getRecentsButton() {
return mCurrentView.findViewById(R.id.recent_apps);
}
@@ -275,6 +306,36 @@
}
}
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (DEBUG) Slog.d(TAG, String.format(
+ "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh));
+ postCheckForInvalidLayout("sizeChanged");
+ super.onSizeChanged(w, h, oldw, oldh);
+ }
+
+ /*
+ @Override
+ protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
+ if (DEBUG) Slog.d(TAG, String.format(
+ "onLayout: %s (%d,%d,%d,%d)",
+ changed?"changed":"notchanged", left, top, right, bottom));
+ super.onLayout(changed, left, top, right, bottom);
+ }
+
+ // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else
+ // fails, any touch on the display will fix the layout.
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (DEBUG) Slog.d(TAG, "onInterceptTouchEvent: " + ev.toString());
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ postCheckForInvalidLayout("touch");
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+ */
+
+
private String getResourceName(int resId) {
if (resId != 0) {
final android.content.res.Resources res = mContext.getResources();
@@ -288,6 +349,10 @@
}
}
+ private void postCheckForInvalidLayout(final String how) {
+ mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget();
+ }
+
private static String visibilityToString(int vis) {
switch (vis) {
case View.INVISIBLE:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 975c372..cc07240 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -55,6 +55,7 @@
float mGlowAlpha = 0f, mGlowScale = 1f, mDrawingAlpha = 1f;
boolean mSupportsLongpress = true;
RectF mRect = new RectF(0f,0f,0f,0f);
+ AnimatorSet mPressedAnim;
Runnable mCheckLongPress = new Runnable() {
public void run() {
@@ -173,7 +174,10 @@
public void setPressed(boolean pressed) {
if (mGlowBG != null) {
if (pressed != isPressed()) {
- AnimatorSet as = new AnimatorSet();
+ if (mPressedAnim != null && mPressedAnim.isRunning()) {
+ mPressedAnim.cancel();
+ }
+ final AnimatorSet as = mPressedAnim = new AnimatorSet();
if (pressed) {
if (mGlowScale < GLOW_MAX_SCALE_FACTOR)
mGlowScale = GLOW_MAX_SCALE_FACTOR;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 666d696..d46ab6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -457,13 +457,13 @@
private final void updateTelephonySignalStrength() {
if (!hasService()) {
if (CHATTY) Slog.d(TAG, "updateTelephonySignalStrength: !hasService()");
- mPhoneSignalIconId = R.drawable.stat_sys_signal_0;
- mDataSignalIconId = R.drawable.stat_sys_signal_0;
+ mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
+ mDataSignalIconId = R.drawable.stat_sys_signal_null;
} else {
if (mSignalStrength == null) {
if (CHATTY) Slog.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null");
- mPhoneSignalIconId = R.drawable.stat_sys_signal_0;
- mDataSignalIconId = R.drawable.stat_sys_signal_0;
+ mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
+ mDataSignalIconId = R.drawable.stat_sys_signal_null;
mContentDescriptionPhoneSignal = mContext.getString(
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0]);
} else {
@@ -888,34 +888,53 @@
String mobileLabel = "";
int N;
- if (mDataConnected) {
- mobileLabel = mNetworkName;
- if (DEBUG) {
- mobileLabel += "yyyyYYYYyyyyYYYY";
- }
- combinedSignalIconId = mDataSignalIconId;
- switch (mDataActivity) {
- case TelephonyManager.DATA_ACTIVITY_IN:
- mMobileActivityIconId = R.drawable.stat_sys_signal_in;
- break;
- case TelephonyManager.DATA_ACTIVITY_OUT:
- mMobileActivityIconId = R.drawable.stat_sys_signal_out;
- break;
- case TelephonyManager.DATA_ACTIVITY_INOUT:
- mMobileActivityIconId = R.drawable.stat_sys_signal_inout;
- break;
- default:
- mMobileActivityIconId = 0;
- break;
+ if (!mHasMobileDataFeature) {
+ mDataSignalIconId = mPhoneSignalIconId = 0;
+ mobileLabel = "";
+ } else {
+ // We want to show the carrier name if in service and either:
+ // - We are connected to mobile data, or
+ // - We are not connected to mobile data, as long as the *reason* packets are not
+ // being routed over that link is that we have better connectivity via wifi.
+ // If data is disconnected for some other reason but wifi is connected, we show nothing.
+ // Otherwise (nothing connected) we show "No internet connection".
+
+ if (mDataConnected) {
+ mobileLabel = mNetworkName;
+ } else if (mWifiConnected) {
+ if (hasService()) {
+ mobileLabel = mNetworkName;
+ } else {
+ mobileLabel = "";
+ }
+ } else {
+ mobileLabel
+ = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
}
- combinedLabel = mobileLabel;
- combinedActivityIconId = mMobileActivityIconId;
- combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon()
- mContentDescriptionCombinedSignal = mContentDescriptionDataType;
- } else {
- mobileLabel = mHasMobileDataFeature ?
- context.getString(R.string.status_bar_settings_signal_meter_disconnected) : "";
+ // Now for things that should only be shown when actually using mobile data.
+ if (mDataConnected) {
+ combinedSignalIconId = mDataSignalIconId;
+ switch (mDataActivity) {
+ case TelephonyManager.DATA_ACTIVITY_IN:
+ mMobileActivityIconId = R.drawable.stat_sys_signal_in;
+ break;
+ case TelephonyManager.DATA_ACTIVITY_OUT:
+ mMobileActivityIconId = R.drawable.stat_sys_signal_out;
+ break;
+ case TelephonyManager.DATA_ACTIVITY_INOUT:
+ mMobileActivityIconId = R.drawable.stat_sys_signal_inout;
+ break;
+ default:
+ mMobileActivityIconId = 0;
+ break;
+ }
+
+ combinedLabel = mobileLabel;
+ combinedActivityIconId = mMobileActivityIconId;
+ combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon()
+ mContentDescriptionCombinedSignal = mContentDescriptionDataType;
+ }
}
if (mWifiConnected) {
@@ -947,6 +966,12 @@
combinedLabel = wifiLabel;
combinedSignalIconId = mWifiIconId; // set by updateWifiIcons()
mContentDescriptionCombinedSignal = mContentDescriptionWifi;
+ } else {
+ if (mHasMobileDataFeature) {
+ wifiLabel = "";
+ } else {
+ wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
+ }
}
if (mBluetoothTethered) {
@@ -967,9 +992,17 @@
mDataTypeIconId = 0;
// combined values from connected wifi take precedence over airplane mode
- if (!mWifiConnected) {
- wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
- combinedLabel = wifiLabel;
+ if (mWifiConnected) {
+ // Suppress "No internet connection." from mobile if wifi connected.
+ mobileLabel = "";
+ } else {
+ if (mHasMobileDataFeature) {
+ // let the mobile icon show "No internet connection."
+ wifiLabel = "";
+ } else {
+ wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
+ combinedLabel = wifiLabel;
+ }
mContentDescriptionCombinedSignal = mContentDescriptionPhoneSignal;
combinedSignalIconId = mDataSignalIconId;
}
@@ -1029,8 +1062,13 @@
N = mPhoneSignalIconViews.size();
for (int i=0; i<N; i++) {
final ImageView v = mPhoneSignalIconViews.get(i);
- v.setImageResource(mPhoneSignalIconId);
- v.setContentDescription(mContentDescriptionPhoneSignal);
+ if (mPhoneSignalIconId == 0) {
+ v.setVisibility(View.GONE);
+ } else {
+ v.setVisibility(View.VISIBLE);
+ v.setImageResource(mPhoneSignalIconId);
+ v.setContentDescription(mContentDescriptionPhoneSignal);
+ }
}
}
@@ -1136,14 +1174,24 @@
N = mWifiLabelViews.size();
for (int i=0; i<N; i++) {
TextView v = mWifiLabelViews.get(i);
- v.setText(wifiLabel);
+ if ("".equals(wifiLabel)) {
+ v.setVisibility(View.GONE);
+ } else {
+ v.setVisibility(View.VISIBLE);
+ v.setText(wifiLabel);
+ }
}
// mobile label
N = mMobileLabelViews.size();
for (int i=0; i<N; i++) {
TextView v = mMobileLabelViews.get(i);
- v.setText(mobileLabel);
+ if ("".equals(mobileLabel)) {
+ v.setVisibility(View.GONE);
+ } else {
+ v.setVisibility(View.VISIBLE);
+ v.setText(mobileLabel);
+ }
}
}
diff --git a/packages/VpnDialogs/AndroidManifest.xml b/packages/VpnDialogs/AndroidManifest.xml
index 8e062b7..46c0f83 100644
--- a/packages/VpnDialogs/AndroidManifest.xml
+++ b/packages/VpnDialogs/AndroidManifest.xml
@@ -5,7 +5,7 @@
<application android:label="VpnDialogs"
android:allowBackup="false" >
<activity android:name=".ConfirmDialog"
- android:theme="@style/transparent">
+ android:theme="@*android:style/Theme.Holo.Dialog.Alert">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -13,7 +13,7 @@
</activity>
<activity android:name=".ManageDialog"
- android:theme="@style/transparent"
+ android:theme="@*android:style/Theme.Holo.Dialog.Alert"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
diff --git a/packages/VpnDialogs/res/values/styles.xml b/packages/VpnDialogs/res/values/styles.xml
index cf10596..e3469ec 100644
--- a/packages/VpnDialogs/res/values/styles.xml
+++ b/packages/VpnDialogs/res/values/styles.xml
@@ -15,13 +15,6 @@
-->
<resources>
-
- <style name="transparent">
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowNoTitle">true</item>
- <item name="android:windowIsFloating">true</item>
- </style>
-
<style name="label">
<item name="android:gravity">center_vertical|right</item>
<item name="android:paddingRight">2mm</item>
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
index c7b4a5f..13d8019 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ConfirmDialog.java
@@ -16,8 +16,6 @@
package com.android.vpndialogs;
-import android.app.Activity;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -32,15 +30,16 @@
import android.widget.ImageView;
import android.widget.TextView;
-public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedChangeListener,
- DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+import com.android.internal.app.AlertActivity;
+
+public class ConfirmDialog extends AlertActivity implements
+ CompoundButton.OnCheckedChangeListener, DialogInterface.OnClickListener {
private static final String TAG = "VpnConfirm";
private String mPackage;
private IConnectivityManager mService;
- private AlertDialog mDialog;
private Button mButton;
@Override
@@ -67,18 +66,17 @@
getString(R.string.prompt, app.loadLabel(pm)));
((CompoundButton) view.findViewById(R.id.check)).setOnCheckedChangeListener(this);
- mDialog = new AlertDialog.Builder(this)
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setTitle(android.R.string.dialog_alert_title)
- .setView(view)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, this)
- .setCancelable(false)
- .create();
- mDialog.setOnDismissListener(this);
- mDialog.show();
+ mAlertParams.mIconId = android.R.drawable.ic_dialog_alert;
+ mAlertParams.mTitle = getText(android.R.string.dialog_alert_title);
+ mAlertParams.mPositiveButtonText = getText(android.R.string.ok);
+ mAlertParams.mPositiveButtonListener = this;
+ mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
+ mAlertParams.mNegativeButtonListener = this;
+ mAlertParams.mView = view;
+ setupAlert();
- mButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
+ getWindow().setCloseOnTouchOutside(false);
+ mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mButton.setEnabled(false);
} catch (Exception e) {
Log.e(TAG, "onResume", e);
@@ -87,12 +85,7 @@
}
@Override
- protected void onPause() {
- super.onPause();
- if (mDialog != null) {
- mDialog.setOnDismissListener(null);
- mDialog.dismiss();
- }
+ public void onBackPressed() {
}
@Override
@@ -103,16 +96,11 @@
@Override
public void onClick(DialogInterface dialog, int which) {
try {
- if (which == AlertDialog.BUTTON_POSITIVE && mService.prepareVpn(null, mPackage)) {
+ if (which == DialogInterface.BUTTON_POSITIVE && mService.prepareVpn(null, mPackage)) {
setResult(RESULT_OK);
}
} catch (Exception e) {
Log.e(TAG, "onClick", e);
}
}
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- finish();
- }
}
diff --git a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
index 7fb1417..2de0251 100644
--- a/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
+++ b/packages/VpnDialogs/src/com/android/vpndialogs/ManageDialog.java
@@ -16,8 +16,6 @@
package com.android.vpndialogs;
-import android.app.Activity;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -35,20 +33,20 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.app.AlertActivity;
import com.android.internal.net.VpnConfig;
import java.io.DataInputStream;
import java.io.FileInputStream;
-public class ManageDialog extends Activity implements Handler.Callback,
- DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
+public class ManageDialog extends AlertActivity implements
+ DialogInterface.OnClickListener, Handler.Callback {
private static final String TAG = "VpnManage";
private VpnConfig mConfig;
private IConnectivityManager mService;
- private AlertDialog mDialog;
private TextView mDuration;
private TextView mDataTransmitted;
private TextView mDataReceived;
@@ -80,31 +78,24 @@
mDataReceived = (TextView) view.findViewById(R.id.data_received);
if (mConfig.user.equals(VpnConfig.LEGACY_VPN)) {
- mDialog = new AlertDialog.Builder(this)
- .setIcon(android.R.drawable.ic_dialog_info)
- .setTitle(R.string.legacy_title)
- .setView(view)
- .setNeutralButton(R.string.disconnect, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
+ mAlertParams.mIconId = android.R.drawable.ic_dialog_info;
+ mAlertParams.mTitle = getText(R.string.legacy_title);
} else {
PackageManager pm = getPackageManager();
ApplicationInfo app = pm.getApplicationInfo(mConfig.user, 0);
- mDialog = new AlertDialog.Builder(this)
- .setIcon(app.loadIcon(pm))
- .setTitle(app.loadLabel(pm))
- .setView(view)
- .setNeutralButton(R.string.disconnect, this)
- .setNegativeButton(android.R.string.cancel, this)
- .create();
+ mAlertParams.mIcon = app.loadIcon(pm);
+ mAlertParams.mTitle = app.loadLabel(pm);
}
-
if (mConfig.configureIntent != null) {
- mDialog.setButton(DialogInterface.BUTTON_POSITIVE,
- getText(R.string.configure), this);
+ mAlertParams.mPositiveButtonText = getText(R.string.configure);
+ mAlertParams.mPositiveButtonListener = this;
}
- mDialog.setOnDismissListener(this);
- mDialog.show();
+ mAlertParams.mNeutralButtonText = getText(R.string.disconnect);
+ mAlertParams.mNeutralButtonListener = this;
+ mAlertParams.mNegativeButtonText = getText(android.R.string.cancel);
+ mAlertParams.mNegativeButtonListener = this;
+ mAlertParams.mView = view;
+ setupAlert();
if (mHandler == null) {
mHandler = new Handler(this);
@@ -119,18 +110,17 @@
@Override
protected void onPause() {
super.onPause();
- if (mDialog != null) {
- mDialog.setOnDismissListener(null);
- mDialog.dismiss();
+ if (!isFinishing()) {
+ finish();
}
}
@Override
public void onClick(DialogInterface dialog, int which) {
try {
- if (which == AlertDialog.BUTTON_POSITIVE) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
mConfig.configureIntent.send();
- } else if (which == AlertDialog.BUTTON_NEUTRAL) {
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
mService.prepareVpn(mConfig.user, VpnConfig.LEGACY_VPN);
}
} catch (Exception e) {
@@ -140,15 +130,10 @@
}
@Override
- public void onDismiss(DialogInterface dialog) {
- finish();
- }
-
- @Override
public boolean handleMessage(Message message) {
mHandler.removeMessages(0);
- if (mDialog.isShowing()) {
+ if (!isFinishing()) {
if (mConfig.startTime != 0) {
long seconds = (SystemClock.elapsedRealtime() - mConfig.startTime) / 1000;
mDuration.setText(String.format("%02d:%02d:%02d",
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 0d0461b..1e9784c 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -855,6 +855,9 @@
case Password:
secure = mLockPatternUtils.isLockPasswordEnabled();
break;
+ case Unknown:
+ // This means no security is set up
+ break;
default:
throw new IllegalStateException("unknown unlock mode " + unlockMode);
}
@@ -877,8 +880,8 @@
// Re-create the unlock screen if necessary. This is primarily required to properly handle
// SIM state changes. This typically happens when this method is called by reset()
- if (mode == Mode.UnlockScreen) {
- final UnlockMode unlockMode = getUnlockMode();
+ final UnlockMode unlockMode = getUnlockMode();
+ if (mode == Mode.UnlockScreen && unlockMode != UnlockMode.Unknown) {
if (force || mUnlockScreen == null || unlockMode != mUnlockScreenMode) {
boolean restartFaceLock = stopFaceLockIfRunning();
recreateUnlockScreen(unlockMode);
@@ -1052,11 +1055,15 @@
break;
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
- // "forgot pattern" button is only available in the pattern mode...
- if (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) {
- currentMode = UnlockMode.Account;
+ if (mLockPatternUtils.isLockPatternEnabled()) {
+ // "forgot pattern" button is only available in the pattern mode...
+ if (mForgotPattern || mLockPatternUtils.isPermanentlyLocked()) {
+ currentMode = UnlockMode.Account;
+ } else {
+ currentMode = UnlockMode.Pattern;
+ }
} else {
- currentMode = UnlockMode.Pattern;
+ currentMode = UnlockMode.Unknown;
}
break;
default:
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index 24a2420..3384661 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -23,6 +23,8 @@
import com.android.internal.widget.multiwaveview.MultiWaveView;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -34,6 +36,7 @@
import android.widget.*;
import android.util.Log;
import android.media.AudioManager;
+import android.os.RemoteException;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -229,8 +232,16 @@
// Start the Camera
Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
- mCallback.goToUnlockScreen();
+ try {
+ ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
+ } catch (RemoteException e) {
+ Log.w(TAG, "can't dismiss keyguard on launch");
+ }
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(TAG, "Camera application not found");
+ }
} else {
toggleRingMode();
mUnlockWidgetMethods.updateResources();
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 43a34d0..2d6e4f8 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -2044,6 +2044,12 @@
mTmpNavigationFrame.offset(mNavigationBarWidth, 0);
}
}
+ // Make sure the content and current rectangles are updated to
+ // account for the restrictions from the navigation bar.
+ mContentTop = mCurTop = mDockTop;
+ mContentBottom = mCurBottom = mDockBottom;
+ mContentLeft = mCurLeft = mDockLeft;
+ mContentRight = mCurRight = mDockRight;
// And compute the final frame.
mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
mTmpNavigationFrame, mTmpNavigationFrame);
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index f5892d4..2d856ad 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1808,7 +1808,7 @@
}
-AudioFlinger::AudioStreamOut* AudioFlinger::PlaybackThread::getOutput()
+AudioFlinger::AudioStreamOut* AudioFlinger::PlaybackThread::getOutput() const
{
Mutex::Autolock _l(mLock);
return mOutput;
@@ -1866,7 +1866,7 @@
bool AudioFlinger::MixerThread::threadLoop()
{
Vector< sp<Track> > tracksToRemove;
- uint32_t mixerStatus = MIXER_IDLE;
+ mixer_state mixerStatus = MIXER_IDLE;
nsecs_t standbyTime = systemTime();
size_t mixBufferSize = mFrameCount * mFrameSize;
// FIXME: Relaxed timing because of a certain device that can't meet latency
@@ -1988,11 +1988,14 @@
if (CC_LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
// mix buffers...
mAudioMixer->process();
- sleepTime = 0;
- // increase sleep time progressively when application underrun condition clears
- if (sleepTimeShift > 0) {
+ // increase sleep time progressively when application underrun condition clears.
+ // Only increase sleep time if the mixer is ready for two consecutive times to avoid
+ // that a steady state of alternating ready/not ready conditions keeps the sleep time
+ // such that we would underrun the audio HAL.
+ if ((sleepTime == 0) && (sleepTimeShift > 0)) {
sleepTimeShift--;
}
+ sleepTime = 0;
standbyTime = systemTime() + kStandbyTimeInNsecs;
//TODO: delay standby when effects have a tail
} else {
@@ -2082,10 +2085,11 @@
}
// prepareTracks_l() must be called with ThreadBase::mLock held
-uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track> >& activeTracks, Vector< sp<Track> > *tracksToRemove)
+AudioFlinger::PlaybackThread::mixer_state AudioFlinger::MixerThread::prepareTracks_l(
+ const SortedVector< wp<Track> >& activeTracks, Vector< sp<Track> > *tracksToRemove)
{
- uint32_t mixerStatus = MIXER_IDLE;
+ mixer_state mixerStatus = MIXER_IDLE;
// find out which tracks need to be processed
size_t count = activeTracks.size();
size_t mixedTracks = 0;
@@ -2609,7 +2613,7 @@
bool AudioFlinger::DirectOutputThread::threadLoop()
{
- uint32_t mixerStatus = MIXER_IDLE;
+ mixer_state mixerStatus = MIXER_IDLE;
sp<Track> trackToRemove;
sp<Track> activeTrack;
nsecs_t standbyTime = systemTime();
@@ -3006,7 +3010,7 @@
bool AudioFlinger::DuplicatingThread::threadLoop()
{
Vector< sp<Track> > tracksToRemove;
- uint32_t mixerStatus = MIXER_IDLE;
+ mixer_state mixerStatus = MIXER_IDLE;
nsecs_t standbyTime = systemTime();
size_t mixBufferSize = mFrameCount*mFrameSize;
SortedVector< sp<OutputTrack> > outputTracks;
@@ -3546,7 +3550,7 @@
sp<ThreadBase> thread = mThread.promote();
if (thread != 0) {
Mutex::Autolock _l(thread->mLock);
- int state = mState;
+ track_state state = mState;
// here the track could be either new, or restarted
// in both cases "unstop" the track
if (mState == PAUSED) {
@@ -3587,7 +3591,7 @@
sp<ThreadBase> thread = mThread.promote();
if (thread != 0) {
Mutex::Autolock _l(thread->mLock);
- int state = mState;
+ track_state state = mState;
if (mState > STOPPED) {
mState = STOPPED;
// If the track is not active (PAUSED and buffers full), flush buffers
@@ -4104,6 +4108,10 @@
mTrack->destroy();
}
+sp<IMemory> AudioFlinger::TrackHandle::getCblk() const {
+ return mTrack->getCblk();
+}
+
status_t AudioFlinger::TrackHandle::start() {
return mTrack->start();
}
@@ -4124,10 +4132,6 @@
mTrack->pause();
}
-sp<IMemory> AudioFlinger::TrackHandle::getCblk() const {
- return mTrack->getCblk();
-}
-
status_t AudioFlinger::TrackHandle::attachAuxEffect(int EffectId)
{
return mTrack->attachAuxEffect(EffectId);
@@ -4234,6 +4238,10 @@
stop();
}
+sp<IMemory> AudioFlinger::RecordHandle::getCblk() const {
+ return mRecordTrack->getCblk();
+}
+
status_t AudioFlinger::RecordHandle::start() {
ALOGV("RecordHandle::start()");
return mRecordTrack->start();
@@ -4244,10 +4252,6 @@
mRecordTrack->stop();
}
-sp<IMemory> AudioFlinger::RecordHandle::getCblk() const {
- return mRecordTrack->getCblk();
-}
-
status_t AudioFlinger::RecordHandle::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
@@ -4894,7 +4898,7 @@
return mTrack;
}
-AudioFlinger::AudioStreamIn* AudioFlinger::RecordThread::getInput()
+AudioFlinger::AudioStreamIn* AudioFlinger::RecordThread::getInput() const
{
Mutex::Autolock _l(mLock);
return mInput;
@@ -5042,6 +5046,7 @@
if (thread->type() != ThreadBase::DUPLICATING) {
AudioStreamOut *out = thread->clearOutput();
+ assert(out != NULL);
// from now on thread->mOutput is NULL
out->hwDev->close_output_stream(out->hwDev, out->stream);
delete out;
@@ -5183,6 +5188,7 @@
thread->exit();
AudioStreamIn *in = thread->clearInput();
+ assert(in != NULL);
// from now on thread->mInput is NULL
in->hwDev->close_input_stream(in->hwDev, in->stream);
delete in;
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 48edfcd..766ba44 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -375,7 +375,7 @@
void* mBufferEnd;
uint32_t mFrameCount;
// we don't really need a lock for these
- int mState;
+ track_state mState;
int mClientTid;
audio_format_t mFormat;
uint32_t mFlags;
@@ -727,7 +727,7 @@
int sessionId,
status_t *status);
- AudioStreamOut* getOutput();
+ AudioStreamOut* getOutput() const;
AudioStreamOut* clearOutput();
virtual audio_stream_t* stream();
@@ -804,7 +804,7 @@
SortedVector< sp<Track> > mTracks;
// mStreamTypes[] uses 1 additional stream type internally for the OutputTrack used by DuplicatingThread
stream_type_t mStreamTypes[AUDIO_STREAM_CNT + 1];
- AudioStreamOut* mOutput;
+ AudioStreamOut *mOutput;
float mMasterVolume;
nsecs_t mLastWriteTime;
int mNumWrites;
@@ -828,7 +828,7 @@
virtual status_t dumpInternals(int fd, const Vector<String16>& args);
protected:
- uint32_t prepareTracks_l(const SortedVector< wp<Track> >& activeTracks,
+ mixer_state prepareTracks_l(const SortedVector< wp<Track> >& activeTracks,
Vector< sp<Track> > *tracksToRemove);
virtual int getTrackName_l();
virtual void deleteTrackName_l(int name);
@@ -836,8 +836,7 @@
virtual uint32_t suspendSleepTimeUs();
AudioMixer* mAudioMixer;
- uint32_t mPrevMixerStatus; // previous status (mixer_state) returned by
- // prepareTracks_l()
+ mixer_state mPrevMixerStatus; // previous status returned by prepareTracks_l()
};
class DirectOutputThread : public PlaybackThread {
@@ -908,12 +907,12 @@
public:
TrackHandle(const sp<PlaybackThread::Track>& track);
virtual ~TrackHandle();
+ virtual sp<IMemory> getCblk() const;
virtual status_t start();
virtual void stop();
virtual void flush();
virtual void mute(bool);
virtual void pause();
- virtual sp<IMemory> getCblk() const;
virtual status_t attachAuxEffect(int effectId);
virtual status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
@@ -994,7 +993,7 @@
status_t start(RecordTrack* recordTrack);
void stop(RecordTrack* recordTrack);
status_t dump(int fd, const Vector<String16>& args);
- AudioStreamIn* getInput();
+ AudioStreamIn* getInput() const;
AudioStreamIn* clearInput();
virtual audio_stream_t* stream();
@@ -1031,9 +1030,9 @@
public:
RecordHandle(const sp<RecordThread::RecordTrack>& recordTrack);
virtual ~RecordHandle();
+ virtual sp<IMemory> getCblk() const;
virtual status_t start();
virtual void stop();
- virtual sp<IMemory> getCblk() const;
virtual status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
private:
@@ -1087,7 +1086,7 @@
void reset_l();
status_t configure();
status_t init();
- uint32_t state() {
+ effect_state state() const {
return mState;
}
uint32_t status() {
@@ -1152,8 +1151,8 @@
effect_descriptor_t mDescriptor;// effect descriptor received from effect engine
effect_config_t mConfig; // input and output audio configuration
effect_handle_t mEffectInterface; // Effect module C API
- status_t mStatus; // initialization status
- uint32_t mState; // current activation state (effect_state)
+ status_t mStatus; // initialization status
+ effect_state mState; // current activation state
Vector< wp<EffectHandle> > mHandles; // list of client handles
uint32_t mMaxDisableWaitCnt; // maximum grace period before forcing an effect off after
// sending disable command.
@@ -1357,17 +1356,21 @@
KeyedVector< int, sp<SuspendedEffectDesc> > mSuspendedEffects;
};
+ // AudioStreamOut and AudioStreamIn are immutable, so their fields are const.
+ // For emphasis, we could also make all pointers to them be "const *",
+ // but that would clutter the code unnecessarily.
+
struct AudioStreamOut {
- audio_hw_device_t *hwDev;
- audio_stream_out_t *stream;
+ audio_hw_device_t* const hwDev;
+ audio_stream_out_t* const stream;
AudioStreamOut(audio_hw_device_t *dev, audio_stream_out_t *out) :
hwDev(dev), stream(out) {}
};
struct AudioStreamIn {
- audio_hw_device_t *hwDev;
- audio_stream_in_t *stream;
+ audio_hw_device_t* const hwDev;
+ audio_stream_in_t* const stream;
AudioStreamIn(audio_hw_device_t *dev, audio_stream_in_t *in) :
hwDev(dev), stream(in) {}
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index aebfd60..31515e1 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -140,6 +140,8 @@
static final int BACKUP_FILE_VERSION = 1;
static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
+ static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
+
// How often we perform a backup pass. Privileged external callers can
// trigger an immediate pass.
private static final long BACKUP_INTERVAL = AlarmManager.INTERVAL_HOUR;
@@ -2325,14 +2327,16 @@
ParcelFileDescriptor mPipe;
int mToken;
boolean mSendApk;
+ boolean mWriteManifest;
FullBackupRunner(PackageInfo pack, IBackupAgent agent, ParcelFileDescriptor pipe,
- int token, boolean sendApk) throws IOException {
+ int token, boolean sendApk, boolean writeManifest) throws IOException {
mPackage = pack;
mAgent = agent;
mPipe = ParcelFileDescriptor.dup(pipe.getFileDescriptor());
mToken = token;
mSendApk = sendApk;
+ mWriteManifest = writeManifest;
}
@Override
@@ -2341,12 +2345,14 @@
BackupDataOutput output = new BackupDataOutput(
mPipe.getFileDescriptor());
- if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
- writeAppManifest(mPackage, mManifestFile, mSendApk);
- FullBackup.backupToTar(mPackage.packageName, null, null,
- mFilesDir.getAbsolutePath(),
- mManifestFile.getAbsolutePath(),
- output);
+ if (mWriteManifest) {
+ if (MORE_DEBUG) Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
+ writeAppManifest(mPackage, mManifestFile, mSendApk);
+ FullBackup.backupToTar(mPackage.packageName, null, null,
+ mFilesDir.getAbsolutePath(),
+ mManifestFile.getAbsolutePath(),
+ output);
+ }
if (mSendApk) {
writeApkToBackup(mPackage, output);
@@ -2433,10 +2439,13 @@
}
}
- // Cull any packages that have indicated that backups are not permitted.
+ // Cull any packages that have indicated that backups are not permitted, as well
+ // as any explicit mention of the 'special' shared-storage agent package (we
+ // handle that one at the end).
for (int i = 0; i < packagesToBackup.size(); ) {
PackageInfo pkg = packagesToBackup.get(i);
- if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
+ if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0
+ || pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE)) {
packagesToBackup.remove(i);
} else {
i++;
@@ -2516,6 +2525,16 @@
return;
}
+ // Shared storage if requested
+ if (mIncludeShared) {
+ try {
+ pkg = mPackageManager.getPackageInfo(SHARED_BACKUP_AGENT_PACKAGE, 0);
+ packagesToBackup.add(pkg);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Unable to find shared-storage backup handler");
+ }
+ }
+
// Now back up the app data via the agent mechanism
int N = packagesToBackup.size();
for (int i = 0; i < N; i++) {
@@ -2523,11 +2542,6 @@
backupOnePackage(pkg, out);
}
- // Shared storage if requested
- if (mIncludeShared) {
- backupSharedStorage();
- }
-
// Done!
finalizeBackup(out);
} catch (RemoteException e) {
@@ -2633,19 +2647,21 @@
if (agent != null) {
ParcelFileDescriptor[] pipes = null;
try {
- pipes = ParcelFileDescriptor.createPipe();
+ pipes = ParcelFileDescriptor.createPipe();
ApplicationInfo app = pkg.applicationInfo;
+ final boolean isSharedStorage = pkg.packageName.equals(SHARED_BACKUP_AGENT_PACKAGE);
final boolean sendApk = mIncludeApks
+ && !isSharedStorage
&& ((app.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0)
&& ((app.flags & ApplicationInfo.FLAG_SYSTEM) == 0 ||
(app.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
- sendOnBackupPackage(pkg.packageName);
+ sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
final int token = generateToken();
FullBackupRunner runner = new FullBackupRunner(pkg, agent, pipes[1],
- token, sendApk);
+ token, sendApk, !isSharedStorage);
pipes[1].close(); // the runner has dup'd it
pipes[1] = null;
Thread t = new Thread(runner);
@@ -2720,33 +2736,6 @@
}
}
- private void backupSharedStorage() throws RemoteException {
- PackageInfo pkg = null;
- try {
- pkg = mPackageManager.getPackageInfo("com.android.sharedstoragebackup", 0);
- IBackupAgent agent = bindToAgentSynchronous(pkg.applicationInfo,
- IApplicationThread.BACKUP_MODE_FULL);
- if (agent != null) {
- sendOnBackupPackage("Shared storage");
-
- final int token = generateToken();
- prepareOperationTimeout(token, TIMEOUT_SHARED_BACKUP_INTERVAL, null);
- agent.doFullBackup(mOutputFile, token, mBackupManagerBinder);
- if (!waitUntilOperationComplete(token)) {
- Slog.e(TAG, "Full backup failed on shared storage");
- } else {
- if (DEBUG) Slog.d(TAG, "Full shared storage backup success");
- }
- } else {
- Slog.e(TAG, "Could not bind to shared storage backup agent");
- }
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "Shared storage backup package not found");
- } finally {
- tearDown(pkg);
- }
- }
-
private void finalizeBackup(OutputStream out) {
try {
// A standard 'tar' EOF sequence: two 512-byte blocks of all zeroes.
@@ -2972,7 +2961,7 @@
// Are we able to restore shared-storage data?
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
- mPackagePolicies.put("com.android.sharedstoragebackup", RestorePolicy.ACCEPT);
+ mPackagePolicies.put(SHARED_BACKUP_AGENT_PACKAGE, RestorePolicy.ACCEPT);
}
FileInputStream rawInStream = null;
@@ -3850,7 +3839,7 @@
info.path, 0, FullBackup.SHARED_PREFIX.length())) {
// File in shared storage. !!! TODO: implement this.
info.path = info.path.substring(FullBackup.SHARED_PREFIX.length());
- info.packageName = "com.android.sharedstoragebackup";
+ info.packageName = SHARED_BACKUP_AGENT_PACKAGE;
info.domain = FullBackup.SHARED_STORAGE_TOKEN;
if (DEBUG) Slog.i(TAG, "File in shared storage: " + info.path);
} else if (FullBackup.APPS_PREFIX.regionMatches(0,
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index b7dc4a2..a372fb8 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -992,11 +992,15 @@
NetworkInfo ni = network.getNetworkInfo();
if (ni.isAvailable() == false) {
- if (DBG) log("special network not available");
if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
+ if (DBG) log("special network not available ni=" + ni.getTypeName());
return Phone.APN_TYPE_NOT_AVAILABLE;
} else {
// else make the attempt anyway - probably giving REQUEST_STARTED below
+ if (DBG) {
+ log("special network not available, but try anyway ni=" +
+ ni.getTypeName());
+ }
}
}
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 4dad209..0bcec2e 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -142,5 +142,5 @@
# ---------------------------
# NetworkStatsService.java
# ---------------------------
-51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3),(dev_history_start|2|3)
-51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3),(dev_history_start|2|3)
+51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 774495a..bb0ac3e 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -51,7 +51,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.WorkSource;
-import android.provider.Settings.SettingNotFoundException;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Log;
@@ -60,6 +59,7 @@
import static android.provider.Settings.System.DIM_SCREEN;
import static android.provider.Settings.System.SCREEN_BRIGHTNESS;
import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE;
+import static android.provider.Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ;
import static android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
import static android.provider.Settings.System.STAY_ON_WHILE_PLUGGED_IN;
@@ -106,6 +106,14 @@
// light sensor events rate in microseconds
private static final int LIGHT_SENSOR_RATE = 1000000;
+ // Expansion of range of light values when applying scale from light
+ // sensor brightness setting, in the [0..255] brightness range.
+ private static final int LIGHT_SENSOR_RANGE_EXPANSION = 20;
+
+ // Scaling factor of the light sensor brightness setting when applying
+ // it to the final brightness.
+ private static final int LIGHT_SENSOR_OFFSET_SCALE = 8;
+
// For debouncing the proximity sensor in milliseconds
private static final int PROXIMITY_SENSOR_DELAY = 1000;
@@ -118,6 +126,9 @@
// Default timeout for screen off, if not found in settings database = 15 seconds.
private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15000;
+ // Screen brightness should always have a value, but just in case...
+ private static final int DEFAULT_SCREEN_BRIGHTNESS = 192;
+
// flags for setPowerState
private static final int SCREEN_ON_BIT = 0x00000001;
private static final int SCREEN_BRIGHT_BIT = 0x00000002;
@@ -150,6 +161,8 @@
static final int ANIM_STEPS = 60/4;
// Slower animation for autobrightness changes
static final int AUTOBRIGHTNESS_ANIM_STEPS = 60;
+ // Number of steps when performing a more immediate brightness change.
+ static final int IMMEDIATE_ANIM_STEPS = 4;
// These magic numbers are the initial state of the LEDs at boot. Ideally
// we should read them from the driver, but our current hardware returns 0
@@ -227,6 +240,7 @@
private boolean mLightSensorPendingDecrease = false;
private boolean mLightSensorPendingIncrease = false;
private float mLightSensorPendingValue = -1;
+ private float mLightSensorAdjustSetting = 0;
private int mLightSensorScreenBrightness = -1;
private int mLightSensorButtonBrightness = -1;
private int mLightSensorKeyboardBrightness = -1;
@@ -240,6 +254,7 @@
// mLastScreenOnTime is the time the screen was last turned on
private long mLastScreenOnTime;
private boolean mPreventScreenOn;
+ private int mScreenBrightnessSetting = DEFAULT_SCREEN_BRIGHTNESS;
private int mScreenBrightnessOverride = -1;
private int mButtonBrightnessOverride = -1;
private int mScreenBrightnessDim;
@@ -460,6 +475,9 @@
// DIM_SCREEN
//mDimScreen = getInt(DIM_SCREEN) != 0;
+ mScreenBrightnessSetting = getInt(SCREEN_BRIGHTNESS, DEFAULT_SCREEN_BRIGHTNESS);
+ mLightSensorAdjustSetting = getFloat(SCREEN_AUTO_BRIGHTNESS_ADJ, 0);
+
// SCREEN_BRIGHTNESS_MODE, default to manual
setScreenBrightnessMode(getInt(SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL));
@@ -624,9 +642,12 @@
+ Settings.System.NAME + "=?) or ("
+ Settings.System.NAME + "=?) or ("
+ Settings.System.NAME + "=?) or ("
+ + Settings.System.NAME + "=?) or ("
+ + Settings.System.NAME + "=?) or ("
+ Settings.System.NAME + "=?)",
- new String[]{STAY_ON_WHILE_PLUGGED_IN, SCREEN_OFF_TIMEOUT, DIM_SCREEN,
- SCREEN_BRIGHTNESS_MODE, WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE},
+ new String[]{STAY_ON_WHILE_PLUGGED_IN, SCREEN_OFF_TIMEOUT, DIM_SCREEN, SCREEN_BRIGHTNESS,
+ SCREEN_BRIGHTNESS_MODE, SCREEN_AUTO_BRIGHTNESS_ADJ,
+ WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE},
null);
mSettings = new ContentQueryMap(settingsCursor, Settings.System.NAME, true, mHandler);
SettingsObserver settingsObserver = new SettingsObserver();
@@ -1163,7 +1184,8 @@
pw.println(" mProximitySensorActive=" + mProximitySensorActive);
pw.println(" mProximityPendingValue=" + mProximityPendingValue);
pw.println(" mLastProximityEventTime=" + mLastProximityEventTime);
- pw.println(" mLightSensorEnabled=" + mLightSensorEnabled);
+ pw.println(" mLightSensorEnabled=" + mLightSensorEnabled
+ + " mLightSensorAdjustSetting=" + mLightSensorAdjustSetting);
pw.println(" mLightSensorValue=" + mLightSensorValue
+ " mLightSensorPendingValue=" + mLightSensorPendingValue);
pw.println(" mLightSensorPendingDecrease=" + mLightSensorPendingDecrease
@@ -2230,20 +2252,15 @@
}
private int getPreferredBrightness() {
- try {
- if (mScreenBrightnessOverride >= 0) {
- return mScreenBrightnessOverride;
- } else if (mLightSensorScreenBrightness >= 0 && mUseSoftwareAutoBrightness
- && mAutoBrightessEnabled) {
- return mLightSensorScreenBrightness;
- }
- final int brightness = Settings.System.getInt(mContext.getContentResolver(),
- SCREEN_BRIGHTNESS);
- // Don't let applications turn the screen all the way off
- return Math.max(brightness, mScreenBrightnessDim);
- } catch (SettingNotFoundException snfe) {
- return Power.BRIGHTNESS_ON;
+ if (mScreenBrightnessOverride >= 0) {
+ return mScreenBrightnessOverride;
+ } else if (mLightSensorScreenBrightness >= 0 && mUseSoftwareAutoBrightness
+ && mAutoBrightessEnabled) {
+ return mLightSensorScreenBrightness;
}
+ final int brightness = mScreenBrightnessSetting;
+ // Don't let applications turn the screen all the way off
+ return Math.max(brightness, mScreenBrightnessDim);
}
private int applyButtonState(int state) {
@@ -2439,7 +2456,34 @@
break;
}
}
- return values[i];
+ // This is the range of brightness values that we can use.
+ final int minval = values[0];
+ final int maxval = values[mAutoBrightnessLevels.length];
+ // This is the range we will be scaling. We put some padding
+ // at the low and high end to give the adjustment a little better
+ // impact on the actual observed value.
+ final int range = (maxval-minval) + LIGHT_SENSOR_RANGE_EXPANSION;
+ // This is the desired brightness value from 0.0 to 1.0.
+ float valf = ((values[i]-minval+(LIGHT_SENSOR_RANGE_EXPANSION/2))/(float)range);
+ // Apply a scaling to the value based on the adjustment.
+ if (mLightSensorAdjustSetting > 0 && mLightSensorAdjustSetting <= 1) {
+ float adj = (float)Math.sqrt(1.0f-mLightSensorAdjustSetting);
+ if (adj <= .00001) {
+ valf = 1;
+ } else {
+ valf /= adj;
+ }
+ } else if (mLightSensorAdjustSetting < 0 && mLightSensorAdjustSetting >= -1) {
+ float adj = (float)Math.sqrt(1.0f+mLightSensorAdjustSetting);
+ valf *= adj;
+ }
+ // Apply an additional offset to the value based on the adjustment.
+ valf += mLightSensorAdjustSetting/LIGHT_SENSOR_OFFSET_SCALE;
+ // Convert the 0.0-1.0 value back to a brightness integer.
+ int val = (int)((valf*range)+minval) - (LIGHT_SENSOR_RANGE_EXPANSION/2);
+ if (val < minval) val = minval;
+ else if (val > maxval) val = maxval;
+ return val;
} catch (Exception e) {
// guard against null pointer or index out of bounds errors
Slog.e(TAG, "getAutoBrightnessValue", e);
@@ -2468,7 +2512,7 @@
int value = (int)mLightSensorPendingValue;
mLightSensorPendingDecrease = false;
mLightSensorPendingIncrease = false;
- lightSensorChangedLocked(value);
+ lightSensorChangedLocked(value, false);
}
}
}
@@ -2485,12 +2529,12 @@
// force lights recalculation
int value = (int)mLightSensorValue;
mLightSensorValue = -1;
- lightSensorChangedLocked(value);
+ lightSensorChangedLocked(value, false);
}
}
}
- private void lightSensorChangedLocked(int value) {
+ private void lightSensorChangedLocked(int value, boolean immediate) {
if (mDebugLightSensor) {
Slog.d(TAG, "lightSensorChangedLocked " + value);
}
@@ -2536,7 +2580,8 @@
if (mAutoBrightessEnabled && mScreenBrightnessOverride < 0) {
if (!mSkippedScreenOn) {
- mScreenBrightness.setTargetLocked(lcdValue, AUTOBRIGHTNESS_ANIM_STEPS,
+ mScreenBrightness.setTargetLocked(lcdValue,
+ immediate ? IMMEDIATE_ANIM_STEPS : AUTOBRIGHTNESS_ANIM_STEPS,
INITIAL_SCREEN_BRIGHTNESS, (int)mScreenBrightness.curValue);
}
}
@@ -2684,7 +2729,7 @@
if (mLightSensorValue >= 0) {
int value = (int)mLightSensorValue;
mLightSensorValue = -1;
- lightSensorChangedLocked(value);
+ lightSensorChangedLocked(value, false);
}
}
userActivity(SystemClock.uptimeMillis(), false, BUTTON_EVENT, true);
@@ -2944,10 +2989,28 @@
Binder.restoreCallingIdentity(identity);
}
- // update our animation state
- synchronized (mLocks) {
- mScreenBrightness.targetValue = brightness;
- mScreenBrightness.jumpToTargetLocked();
+ mScreenBrightness.targetValue = brightness;
+ mScreenBrightness.jumpToTargetLocked();
+ }
+ }
+
+ public void setAutoBrightnessAdjustment(float adj) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+ synchronized (mLocks) {
+ mLightSensorAdjustSetting = adj;
+ if (mSensorManager != null && mLightSensorEnabled) {
+ // clear calling identity so sensor manager battery stats are accurate
+ long identity = Binder.clearCallingIdentity();
+ try {
+ // force recompute of backlight values
+ if (mLightSensorValue >= 0) {
+ int value = (int)mLightSensorValue;
+ mLightSensorValue = -1;
+ handleLightSensorValue(value, true);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
}
@@ -3062,7 +3125,7 @@
if (mLightSensorValue >= 0) {
int value = (int)mLightSensorValue;
mLightSensorValue = -1;
- handleLightSensorValue(value);
+ handleLightSensorValue(value, true);
}
mSensorManager.registerListener(mLightListener, mLightSensor,
LIGHT_SENSOR_RATE);
@@ -3122,7 +3185,7 @@
}
};
- private void handleLightSensorValue(int value) {
+ private void handleLightSensorValue(int value, boolean immediate) {
long milliseconds = SystemClock.elapsedRealtime();
if (mLightSensorValue == -1 ||
milliseconds < mLastScreenOnTime + mLightSensorWarmupTime) {
@@ -3130,7 +3193,7 @@
mHandler.removeCallbacks(mAutoBrightnessTask);
mLightSensorPendingDecrease = false;
mLightSensorPendingIncrease = false;
- lightSensorChangedLocked(value);
+ lightSensorChangedLocked(value, immediate);
} else {
if ((value > mLightSensorValue && mLightSensorPendingDecrease) ||
(value < mLightSensorValue && mLightSensorPendingIncrease) ||
@@ -3160,7 +3223,7 @@
if (isScreenTurningOffLocked()) {
return;
}
- handleLightSensorValue((int)event.values[0]);
+ handleLightSensorValue((int)event.values[0], false);
}
}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index 51adebe..a71ccb5 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1573,6 +1573,9 @@
private long getTotalBytes(NetworkTemplate template, long start, long end) {
try {
return mNetworkStats.getSummaryForNetwork(template, start, end).getTotalBytes();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "problem reading network stats: " + e);
+ return 0;
} catch (RemoteException e) {
// ignored; service lives in system_server
return 0;
diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java
new file mode 100644
index 0000000..70038d9
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkStatsCollection.java
@@ -0,0 +1,510 @@
+/*
+ * 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.server.net;
+
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.TrafficStats.UID_REMOVED;
+
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.text.format.DateUtils;
+
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Objects;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import libcore.io.IoUtils;
+
+/**
+ * Collection of {@link NetworkStatsHistory}, stored based on combined key of
+ * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
+ */
+public class NetworkStatsCollection implements FileRotator.Reader {
+ private static final String TAG = "NetworkStatsCollection";
+
+ /** File header magic number: "ANET" */
+ private static final int FILE_MAGIC = 0x414E4554;
+
+ private static final int VERSION_NETWORK_INIT = 1;
+
+ private static final int VERSION_UID_INIT = 1;
+ private static final int VERSION_UID_WITH_IDENT = 2;
+ private static final int VERSION_UID_WITH_TAG = 3;
+ private static final int VERSION_UID_WITH_SET = 4;
+
+ private static final int VERSION_UNIFIED_INIT = 16;
+
+ private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap();
+
+ private long mBucketDuration;
+
+ private long mStartMillis;
+ private long mEndMillis;
+ private long mTotalBytes;
+ private boolean mDirty;
+
+ public NetworkStatsCollection(long bucketDuration) {
+ mBucketDuration = bucketDuration;
+ reset();
+ }
+
+ public void reset() {
+ mStats.clear();
+ mStartMillis = Long.MAX_VALUE;
+ mEndMillis = Long.MIN_VALUE;
+ mTotalBytes = 0;
+ mDirty = false;
+ }
+
+ public long getStartMillis() {
+ return mStartMillis;
+ }
+
+ public long getEndMillis() {
+ return mEndMillis;
+ }
+
+ public long getTotalBytes() {
+ return mTotalBytes;
+ }
+
+ public boolean isDirty() {
+ return mDirty;
+ }
+
+ public void clearDirty() {
+ mDirty = false;
+ }
+
+ public boolean isEmpty() {
+ return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
+ }
+
+ /**
+ * Combine all {@link NetworkStatsHistory} in this collection which match
+ * the requested parameters.
+ */
+ public NetworkStatsHistory getHistory(
+ NetworkTemplate template, int uid, int set, int tag, int fields) {
+ final NetworkStatsHistory combined = new NetworkStatsHistory(
+ mBucketDuration, estimateBuckets(), fields);
+ for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) {
+ final Key key = entry.getKey();
+ final boolean setMatches = set == SET_ALL || key.set == set;
+ if (key.uid == uid && setMatches && key.tag == tag
+ && templateMatches(template, key.ident)) {
+ combined.recordEntireHistory(entry.getValue());
+ }
+ }
+ return combined;
+ }
+
+ /**
+ * Summarize all {@link NetworkStatsHistory} in this collection which match
+ * the requested parameters.
+ */
+ public NetworkStats getSummary(NetworkTemplate template, long start, long end) {
+ final long now = System.currentTimeMillis();
+
+ final NetworkStats stats = new NetworkStats(end - start, 24);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ NetworkStatsHistory.Entry historyEntry = null;
+
+ for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) {
+ final Key key = mapEntry.getKey();
+ if (templateMatches(template, key.ident)) {
+ final NetworkStatsHistory history = mapEntry.getValue();
+ historyEntry = history.getValues(start, end, now, historyEntry);
+
+ entry.iface = IFACE_ALL;
+ entry.uid = key.uid;
+ entry.set = key.set;
+ entry.tag = key.tag;
+ entry.rxBytes = historyEntry.rxBytes;
+ entry.rxPackets = historyEntry.rxPackets;
+ entry.txBytes = historyEntry.txBytes;
+ entry.txPackets = historyEntry.txPackets;
+ entry.operations = historyEntry.operations;
+
+ if (!entry.isEmpty()) {
+ stats.combineValues(entry);
+ }
+ }
+ }
+
+ return stats;
+ }
+
+ /**
+ * Record given {@link NetworkStats.Entry} into this collection.
+ */
+ public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
+ long end, NetworkStats.Entry entry) {
+ noteRecordedHistory(start, end, entry.rxBytes + entry.txBytes);
+ findOrCreateHistory(ident, uid, set, tag).recordData(start, end, entry);
+ }
+
+ /**
+ * Record given {@link NetworkStatsHistory} into this collection.
+ */
+ private void recordHistory(Key key, NetworkStatsHistory history) {
+ if (history.size() == 0) return;
+ noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
+
+ final NetworkStatsHistory existing = mStats.get(key);
+ if (existing != null) {
+ existing.recordEntireHistory(history);
+ } else {
+ mStats.put(key, history);
+ }
+ }
+
+ /**
+ * Record all {@link NetworkStatsHistory} contained in the given collection
+ * into this collection.
+ */
+ public void recordCollection(NetworkStatsCollection another) {
+ for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) {
+ recordHistory(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private NetworkStatsHistory findOrCreateHistory(
+ NetworkIdentitySet ident, int uid, int set, int tag) {
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory existing = mStats.get(key);
+
+ // update when no existing, or when bucket duration changed
+ NetworkStatsHistory updated = null;
+ if (existing == null) {
+ updated = new NetworkStatsHistory(mBucketDuration, 10);
+ } else if (existing.getBucketDuration() != mBucketDuration) {
+ updated = new NetworkStatsHistory(existing, mBucketDuration);
+ }
+
+ if (updated != null) {
+ mStats.put(key, updated);
+ return updated;
+ } else {
+ return existing;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void read(InputStream in) throws IOException {
+ read(new DataInputStream(in));
+ }
+
+ public void read(DataInputStream in) throws IOException {
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_UNIFIED_INIT: {
+ // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+ final int identSize = in.readInt();
+ for (int i = 0; i < identSize; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+ final int size = in.readInt();
+ for (int j = 0; j < size; j++) {
+ final int uid = in.readInt();
+ final int set = in.readInt();
+ final int tag = in.readInt();
+
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+ recordHistory(key, history);
+ }
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ }
+
+ public void write(DataOutputStream out) throws IOException {
+ // cluster key lists grouped by ident
+ final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
+ for (Key key : mStats.keySet()) {
+ ArrayList<Key> keys = keysByIdent.get(key.ident);
+ if (keys == null) {
+ keys = Lists.newArrayList();
+ keysByIdent.put(key.ident, keys);
+ }
+ keys.add(key);
+ }
+
+ out.writeInt(FILE_MAGIC);
+ out.writeInt(VERSION_UNIFIED_INIT);
+
+ out.writeInt(keysByIdent.size());
+ for (NetworkIdentitySet ident : keysByIdent.keySet()) {
+ final ArrayList<Key> keys = keysByIdent.get(ident);
+ ident.writeToStream(out);
+
+ out.writeInt(keys.size());
+ for (Key key : keys) {
+ final NetworkStatsHistory history = mStats.get(key);
+ out.writeInt(key.uid);
+ out.writeInt(key.set);
+ out.writeInt(key.tag);
+ history.writeToStream(out);
+ }
+ }
+
+ out.flush();
+ }
+
+ @Deprecated
+ public void readLegacyNetwork(File file) throws IOException {
+ final AtomicFile inputFile = new AtomicFile(file);
+
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_NETWORK_INIT: {
+ // network := size *(NetworkIdentitySet NetworkStatsHistory)
+ final int size = in.readInt();
+ for (int i = 0; i < size; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+ final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
+ recordHistory(key, history);
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // missing stats is okay, probably first boot
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ @Deprecated
+ public void readLegacyUid(File file, boolean onlyTags) throws IOException {
+ final AtomicFile inputFile = new AtomicFile(file);
+
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+
+ // verify file magic header intact
+ final int magic = in.readInt();
+ if (magic != FILE_MAGIC) {
+ throw new ProtocolException("unexpected magic: " + magic);
+ }
+
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_UID_INIT: {
+ // uid := size *(UID NetworkStatsHistory)
+
+ // drop this data version, since we don't have a good
+ // mapping into NetworkIdentitySet.
+ break;
+ }
+ case VERSION_UID_WITH_IDENT: {
+ // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
+
+ // drop this data version, since this version only existed
+ // for a short time.
+ break;
+ }
+ case VERSION_UID_WITH_TAG:
+ case VERSION_UID_WITH_SET: {
+ // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+ final int identSize = in.readInt();
+ for (int i = 0; i < identSize; i++) {
+ final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+ final int size = in.readInt();
+ for (int j = 0; j < size; j++) {
+ final int uid = in.readInt();
+ final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
+ : SET_DEFAULT;
+ final int tag = in.readInt();
+
+ final Key key = new Key(ident, uid, set, tag);
+ final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+ if ((tag == TAG_NONE) != onlyTags) {
+ recordHistory(key, history);
+ }
+ }
+ }
+ break;
+ }
+ default: {
+ throw new ProtocolException("unexpected version: " + version);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // missing stats is okay, probably first boot
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ /**
+ * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
+ * moving any {@link NetworkStats#TAG_NONE} series to
+ * {@link TrafficStats#UID_REMOVED}.
+ */
+ public void removeUid(int uid) {
+ final ArrayList<Key> knownKeys = Lists.newArrayList();
+ knownKeys.addAll(mStats.keySet());
+
+ // migrate all UID stats into special "removed" bucket
+ for (Key key : knownKeys) {
+ if (key.uid == uid) {
+ // only migrate combined TAG_NONE history
+ if (key.tag == TAG_NONE) {
+ final NetworkStatsHistory uidHistory = mStats.get(key);
+ final NetworkStatsHistory removedHistory = findOrCreateHistory(
+ key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
+ removedHistory.recordEntireHistory(uidHistory);
+ }
+ mStats.remove(key);
+ mDirty = true;
+ }
+ }
+ }
+
+ private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
+ if (startMillis < mStartMillis) mStartMillis = startMillis;
+ if (endMillis > mEndMillis) mEndMillis = endMillis;
+ mTotalBytes += totalBytes;
+ mDirty = true;
+ }
+
+ private int estimateBuckets() {
+ return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5)
+ / mBucketDuration);
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ final ArrayList<Key> keys = Lists.newArrayList();
+ keys.addAll(mStats.keySet());
+ Collections.sort(keys);
+
+ for (Key key : keys) {
+ pw.print("ident="); pw.print(key.ident.toString());
+ pw.print(" uid="); pw.print(key.uid);
+ pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
+ pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
+
+ final NetworkStatsHistory history = mStats.get(key);
+ pw.increaseIndent();
+ history.dump(pw, true);
+ pw.decreaseIndent();
+ }
+ }
+
+ /**
+ * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
+ * in the given {@link NetworkIdentitySet}.
+ */
+ private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
+ for (NetworkIdentity ident : identSet) {
+ if (template.matches(ident)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static class Key implements Comparable<Key> {
+ public final NetworkIdentitySet ident;
+ public final int uid;
+ public final int set;
+ public final int tag;
+
+ private final int hashCode;
+
+ public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
+ this.ident = ident;
+ this.uid = uid;
+ this.set = set;
+ this.tag = tag;
+ hashCode = Objects.hashCode(ident, uid, set, tag);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Key) {
+ final Key key = (Key) obj;
+ return uid == key.uid && set == key.set && tag == key.tag
+ && Objects.equal(ident, key.ident);
+ }
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo(Key another) {
+ return Integer.compare(uid, another.uid);
+ }
+ }
+}
diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java
new file mode 100644
index 0000000..e7ba358
--- /dev/null
+++ b/services/java/com/android/server/net/NetworkStatsRecorder.java
@@ -0,0 +1,341 @@
+/*
+ * 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.server.net;
+
+import static android.net.NetworkStats.TAG_NONE;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
+import com.google.android.collect.Sets;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * Logic to record deltas between periodic {@link NetworkStats} snapshots into
+ * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
+ * Keeps pending changes in memory until they pass a specific threshold, in
+ * bytes. Uses {@link FileRotator} for persistence logic.
+ * <p>
+ * Not inherently thread safe.
+ */
+public class NetworkStatsRecorder {
+ private static final String TAG = "NetworkStatsRecorder";
+ private static final boolean LOGD = true;
+
+ private final FileRotator mRotator;
+ private final NonMonotonicObserver<String> mObserver;
+ private final String mCookie;
+
+ private final long mBucketDuration;
+ private final long mPersistThresholdBytes;
+ private final boolean mOnlyTags;
+
+ private NetworkStats mLastSnapshot;
+
+ private final NetworkStatsCollection mPending;
+ private final NetworkStatsCollection mSinceBoot;
+
+ private final CombiningRewriter mPendingRewriter;
+
+ private WeakReference<NetworkStatsCollection> mComplete;
+
+ public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
+ String cookie, long bucketDuration, long persistThresholdBytes, boolean onlyTags) {
+ mRotator = checkNotNull(rotator, "missing FileRotator");
+ mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
+ mCookie = cookie;
+
+ mBucketDuration = bucketDuration;
+ mPersistThresholdBytes = persistThresholdBytes;
+ mOnlyTags = onlyTags;
+
+ mPending = new NetworkStatsCollection(bucketDuration);
+ mSinceBoot = new NetworkStatsCollection(bucketDuration);
+
+ mPendingRewriter = new CombiningRewriter(mPending);
+ }
+
+ public void resetLocked() {
+ mLastSnapshot = null;
+ mPending.reset();
+ mSinceBoot.reset();
+ mComplete.clear();
+ }
+
+ public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
+ return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+ }
+
+ /**
+ * Load complete history represented by {@link FileRotator}. Caches
+ * internally as a {@link WeakReference}, and updated with future
+ * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
+ * as reference is valid.
+ */
+ public NetworkStatsCollection getOrLoadCompleteLocked() {
+ NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+ if (complete == null) {
+ if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
+ try {
+ complete = new NetworkStatsCollection(mBucketDuration);
+ mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
+ complete.recordCollection(mPending);
+ mComplete = new WeakReference<NetworkStatsCollection>(complete);
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem completely reading network stats", e);
+ }
+ }
+ return complete;
+ }
+
+ /**
+ * Record any delta that occurred since last {@link NetworkStats} snapshot,
+ * using the given {@link Map} to identify network interfaces. First
+ * snapshot is considered bootstrap, and is not counted as delta.
+ */
+ public void recordSnapshotLocked(NetworkStats snapshot,
+ Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
+ final HashSet<String> unknownIfaces = Sets.newHashSet();
+
+ // assume first snapshot is bootstrap and don't record
+ if (mLastSnapshot == null) {
+ mLastSnapshot = snapshot;
+ return;
+ }
+
+ final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+
+ final NetworkStats delta = NetworkStats.subtract(
+ snapshot, mLastSnapshot, mObserver, mCookie);
+ final long end = currentTimeMillis;
+ final long start = end - delta.getElapsedRealtime();
+
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < delta.size(); i++) {
+ entry = delta.getValues(i, entry);
+ final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
+ if (ident == null) {
+ unknownIfaces.add(entry.iface);
+ continue;
+ }
+
+ // skip when no delta occured
+ if (entry.isEmpty()) continue;
+
+ // only record tag data when requested
+ if ((entry.tag == TAG_NONE) != mOnlyTags) {
+ mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+
+ // also record against boot stats when present
+ if (mSinceBoot != null) {
+ mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+ }
+
+ // also record against complete dataset when present
+ if (complete != null) {
+ complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+ }
+ }
+ }
+
+ mLastSnapshot = snapshot;
+
+ if (LOGD && unknownIfaces.size() > 0) {
+ Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
+ }
+ }
+
+ /**
+ * Consider persisting any pending deltas, if they are beyond
+ * {@link #mPersistThresholdBytes}.
+ */
+ public void maybePersistLocked(long currentTimeMillis) {
+ final long pendingBytes = mPending.getTotalBytes();
+ if (pendingBytes >= mPersistThresholdBytes) {
+ forcePersistLocked(currentTimeMillis);
+ } else {
+ mRotator.maybeRotate(currentTimeMillis);
+ }
+ }
+
+ /**
+ * Force persisting any pending deltas.
+ */
+ public void forcePersistLocked(long currentTimeMillis) {
+ if (mPending.isDirty()) {
+ if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
+ try {
+ mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
+ mRotator.maybeRotate(currentTimeMillis);
+ mPending.reset();
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem persisting pending stats", e);
+ }
+ }
+ }
+
+ /**
+ * Remove the given UID from all {@link FileRotator} history, migrating it
+ * to {@link TrafficStats#UID_REMOVED}.
+ */
+ public void removeUidLocked(int uid) {
+ try {
+ // process all existing data to migrate uid
+ mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid));
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem removing UID " + uid, e);
+ }
+
+ // clear UID from current stats snapshot
+ if (mLastSnapshot != null) {
+ mLastSnapshot = mLastSnapshot.withoutUid(uid);
+ }
+ }
+
+ /**
+ * Rewriter that will combine current {@link NetworkStatsCollection} values
+ * with anything read from disk, and write combined set to disk. Clears the
+ * original {@link NetworkStatsCollection} when finished writing.
+ */
+ private static class CombiningRewriter implements FileRotator.Rewriter {
+ private final NetworkStatsCollection mCollection;
+
+ public CombiningRewriter(NetworkStatsCollection collection) {
+ mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
+ }
+
+ /** {@inheritDoc} */
+ public void reset() {
+ // ignored
+ }
+
+ /** {@inheritDoc} */
+ public void read(InputStream in) throws IOException {
+ mCollection.read(in);
+ }
+
+ /** {@inheritDoc} */
+ public boolean shouldWrite() {
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public void write(OutputStream out) throws IOException {
+ mCollection.write(new DataOutputStream(out));
+ mCollection.reset();
+ }
+ }
+
+ /**
+ * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
+ * the requested UID, only writing data back when modified.
+ */
+ public static class RemoveUidRewriter implements FileRotator.Rewriter {
+ private final NetworkStatsCollection mTemp;
+ private final int mUid;
+
+ public RemoveUidRewriter(long bucketDuration, int uid) {
+ mTemp = new NetworkStatsCollection(bucketDuration);
+ mUid = uid;
+ }
+
+ /** {@inheritDoc} */
+ public void reset() {
+ mTemp.reset();
+ }
+
+ /** {@inheritDoc} */
+ public void read(InputStream in) throws IOException {
+ mTemp.read(in);
+ mTemp.clearDirty();
+ mTemp.removeUid(mUid);
+ }
+
+ /** {@inheritDoc} */
+ public boolean shouldWrite() {
+ return mTemp.isDirty();
+ }
+
+ /** {@inheritDoc} */
+ public void write(OutputStream out) throws IOException {
+ mTemp.write(new DataOutputStream(out));
+ }
+ }
+
+ public void importLegacyNetworkLocked(File file) throws IOException {
+ // legacy file still exists; start empty to avoid double importing
+ mRotator.deleteAll();
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+ collection.readLegacyNetwork(file);
+
+ final long startMillis = collection.getStartMillis();
+ final long endMillis = collection.getEndMillis();
+
+ if (!collection.isEmpty()) {
+ // process legacy data, creating active file at starting time, then
+ // using end time to possibly trigger rotation.
+ mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
+ mRotator.maybeRotate(endMillis);
+ }
+ }
+
+ public void importLegacyUidLocked(File file) throws IOException {
+ // legacy file still exists; start empty to avoid double importing
+ mRotator.deleteAll();
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+ collection.readLegacyUid(file, mOnlyTags);
+
+ final long startMillis = collection.getStartMillis();
+ final long endMillis = collection.getEndMillis();
+
+ if (!collection.isEmpty()) {
+ // process legacy data, creating active file at starting time, then
+ // using end time to possibly trigger rotation.
+ mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
+ mRotator.maybeRotate(endMillis);
+ }
+ }
+
+ public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
+ pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
+ if (fullHistory) {
+ pw.println("Complete history:");
+ getOrLoadCompleteLocked().dump(pw);
+ } else {
+ pw.println("History since boot:");
+ mSinceBoot.dump(pw);
+ }
+ }
+}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index eeb7fec..c9b79e8 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -34,14 +34,18 @@
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifi;
-import static android.net.TrafficStats.UID_REMOVED;
-import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
-import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
+import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION;
+import static android.provider.Settings.Secure.NETSTATS_DEV_DELETE_AGE;
+import static android.provider.Settings.Secure.NETSTATS_DEV_PERSIST_BYTES;
+import static android.provider.Settings.Secure.NETSTATS_DEV_ROTATE_AGE;
+import static android.provider.Settings.Secure.NETSTATS_GLOBAL_ALERT_BYTES;
import static android.provider.Settings.Secure.NETSTATS_POLL_INTERVAL;
-import static android.provider.Settings.Secure.NETSTATS_TAG_MAX_HISTORY;
+import static android.provider.Settings.Secure.NETSTATS_SAMPLE_ENABLED;
+import static android.provider.Settings.Secure.NETSTATS_TIME_CACHE_MAX_AGE;
import static android.provider.Settings.Secure.NETSTATS_UID_BUCKET_DURATION;
-import static android.provider.Settings.Secure.NETSTATS_UID_MAX_HISTORY;
+import static android.provider.Settings.Secure.NETSTATS_UID_DELETE_AGE;
+import static android.provider.Settings.Secure.NETSTATS_UID_PERSIST_BYTES;
+import static android.provider.Settings.Secure.NETSTATS_UID_ROTATE_AGE;
import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
import static android.telephony.PhoneStateListener.LISTEN_NONE;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
@@ -61,12 +65,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.net.IConnectivityManager;
import android.net.INetworkManagementEventObserver;
import android.net.INetworkStatsService;
+import android.net.LinkProperties;
import android.net.NetworkIdentity;
import android.net.NetworkInfo;
import android.net.NetworkState;
@@ -74,6 +76,7 @@
import android.net.NetworkStats.NonMonotonicObserver;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.TrafficStats;
import android.os.Binder;
import android.os.DropBoxManager;
import android.os.Environment;
@@ -94,32 +97,18 @@
import android.util.SparseIntArray;
import android.util.TrustedTime;
-import com.android.internal.os.AtomicFile;
-import com.android.internal.util.Objects;
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.EventLogTags;
import com.android.server.connectivity.Tethering;
-import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.net.ProtocolException;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Random;
-
-import libcore.io.IoUtils;
/**
* Collect and persist detailed network statistics, and provide this data to
@@ -127,16 +116,8 @@
*/
public class NetworkStatsService extends INetworkStatsService.Stub {
private static final String TAG = "NetworkStats";
- private static final boolean LOGD = false;
- private static final boolean LOGV = false;
-
- /** File header magic number: "ANET" */
- private static final int FILE_MAGIC = 0x414E4554;
- private static final int VERSION_NETWORK_INIT = 1;
- private static final int VERSION_UID_INIT = 1;
- private static final int VERSION_UID_WITH_IDENT = 2;
- private static final int VERSION_UID_WITH_TAG = 3;
- private static final int VERSION_UID_WITH_SET = 4;
+ private static final boolean LOGD = true;
+ private static final boolean LOGV = true;
private static final int MSG_PERFORM_POLL = 1;
private static final int MSG_UPDATE_IFACES = 2;
@@ -147,9 +128,6 @@
private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
private static final int FLAG_PERSIST_FORCE = 0x100;
- /** Sample recent usage after each poll event. */
- private static final boolean ENABLE_SAMPLE_AFTER_POLL = true;
-
private static final String TAG_NETSTATS_ERROR = "netstats_error";
private final Context mContext;
@@ -159,10 +137,12 @@
private final TelephonyManager mTeleManager;
private final NetworkStatsSettings mSettings;
+ private final File mSystemDir;
+ private final File mBaseDir;
+
private final PowerManager.WakeLock mWakeLock;
private IConnectivityManager mConnManager;
- private DropBoxManager mDropBox;
// @VisibleForTesting
public static final String ACTION_NETWORK_STATS_POLL =
@@ -172,71 +152,76 @@
private PendingIntent mPollIntent;
- // TODO: trim empty history objects entirely
-
private static final long KB_IN_BYTES = 1024;
private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES;
+ private static final String PREFIX_DEV = "dev";
+ private static final String PREFIX_UID = "uid";
+ private static final String PREFIX_UID_TAG = "uid_tag";
+
/**
* Settings that can be changed externally.
*/
public interface NetworkStatsSettings {
public long getPollInterval();
- public long getPersistThreshold();
- public long getNetworkBucketDuration();
- public long getNetworkMaxHistory();
- public long getUidBucketDuration();
- public long getUidMaxHistory();
- public long getTagMaxHistory();
public long getTimeCacheMaxAge();
+ public long getGlobalAlertBytes();
+ public boolean getSampleEnabled();
+
+ public static class Config {
+ public final long bucketDuration;
+ public final long persistBytes;
+ public final long rotateAgeMillis;
+ public final long deleteAgeMillis;
+
+ public Config(long bucketDuration, long persistBytes, long rotateAgeMillis,
+ long deleteAgeMillis) {
+ this.bucketDuration = bucketDuration;
+ this.persistBytes = persistBytes;
+ this.rotateAgeMillis = rotateAgeMillis;
+ this.deleteAgeMillis = deleteAgeMillis;
+ }
+ }
+
+ public Config getDevConfig();
+ public Config getUidConfig();
+ public Config getUidTagConfig();
}
private final Object mStatsLock = new Object();
/** Set of currently active ifaces. */
private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
- /** Set of historical {@code dev} stats for known networks. */
- private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkDevStats = Maps.newHashMap();
- /** Set of historical {@code xtables} stats for known networks. */
- private HashMap<NetworkIdentitySet, NetworkStatsHistory> mNetworkXtStats = Maps.newHashMap();
- /** Set of historical {@code xtables} stats for known UIDs. */
- private HashMap<UidStatsKey, NetworkStatsHistory> mUidStats = Maps.newHashMap();
+ /** Current default active iface. */
+ private String mActiveIface;
- /** Flag if {@link #mNetworkDevStats} have been loaded from disk. */
- private boolean mNetworkStatsLoaded = false;
- /** Flag if {@link #mUidStats} have been loaded from disk. */
- private boolean mUidStatsLoaded = false;
+ private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
+ new DropBoxNonMonotonicObserver();
- private NetworkStats mLastPollNetworkDevSnapshot;
- private NetworkStats mLastPollNetworkXtSnapshot;
- private NetworkStats mLastPollUidSnapshot;
- private NetworkStats mLastPollOperationsSnapshot;
+ private NetworkStatsRecorder mDevRecorder;
+ private NetworkStatsRecorder mUidRecorder;
+ private NetworkStatsRecorder mUidTagRecorder;
- private NetworkStats mLastPersistNetworkDevSnapshot;
- private NetworkStats mLastPersistNetworkXtSnapshot;
- private NetworkStats mLastPersistUidSnapshot;
+ /** Cached {@link #mDevRecorder} stats. */
+ private NetworkStatsCollection mDevStatsCached;
/** Current counter sets for each UID. */
private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
/** Data layer operation counters for splicing into other structures. */
- private NetworkStats mOperations = new NetworkStats(0L, 10);
+ private NetworkStats mUidOperations = new NetworkStats(0L, 10);
private final HandlerThread mHandlerThread;
private final Handler mHandler;
- private final AtomicFile mNetworkDevFile;
- private final AtomicFile mNetworkXtFile;
- private final AtomicFile mUidFile;
-
public NetworkStatsService(
Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context),
- getSystemDir(), new DefaultNetworkStatsSettings(context));
+ getDefaultSystemDir(), new DefaultNetworkStatsSettings(context));
}
- private static File getSystemDir() {
+ private static File getDefaultSystemDir() {
return new File(Environment.getDataDirectory(), "system");
}
@@ -258,9 +243,9 @@
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback);
- mNetworkDevFile = new AtomicFile(new File(systemDir, "netstats.bin"));
- mNetworkXtFile = new AtomicFile(new File(systemDir, "netstats_xt.bin"));
- mUidFile = new AtomicFile(new File(systemDir, "netstats_uid.bin"));
+ mSystemDir = checkNotNull(systemDir);
+ mBaseDir = new File(systemDir, "netstats");
+ mBaseDir.mkdirs();
}
public void bindConnectivityManager(IConnectivityManager connManager) {
@@ -273,17 +258,22 @@
return;
}
- synchronized (mStatsLock) {
- // read historical network stats from disk, since policy service
- // might need them right away. we delay loading detailed UID stats
- // until actually needed.
- readNetworkDevStatsLocked();
- readNetworkXtStatsLocked();
- mNetworkStatsLoaded = true;
- }
+ // create data recorders along with historical rotators
+ mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
+ mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
+ mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
- // bootstrap initial stats to prevent double-counting later
- bootstrapStats();
+ synchronized (mStatsLock) {
+ // upgrade any legacy stats, migrating them to rotated files
+ maybeUpgradeLegacyStatsLocked();
+
+ // read historical network stats from disk, since policy service
+ // might need them right away.
+ mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked();
+
+ // bootstrap initial stats to prevent double-counting later
+ bootstrapStatsLocked();
+ }
// watch for network interfaces to be claimed
final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE);
@@ -317,8 +307,14 @@
registerPollAlarmLocked();
registerGlobalAlert();
+ }
- mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
+ private NetworkStatsRecorder buildRecorder(
+ String prefix, NetworkStatsSettings.Config config, boolean includeTags) {
+ return new NetworkStatsRecorder(
+ new FileRotator(mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+ mNonMonotonicObserver, prefix, config.bucketDuration, config.persistBytes,
+ includeTags);
}
private void shutdownLocked() {
@@ -330,18 +326,44 @@
mTeleManager.listen(mPhoneListener, LISTEN_NONE);
- if (mNetworkStatsLoaded) {
- writeNetworkDevStatsLocked();
- writeNetworkXtStatsLocked();
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
+ // persist any pending stats
+ mDevRecorder.forcePersistLocked(currentTime);
+ mUidRecorder.forcePersistLocked(currentTime);
+ mUidTagRecorder.forcePersistLocked(currentTime);
+
+ mDevRecorder = null;
+ mUidRecorder = null;
+ mUidTagRecorder = null;
+
+ mDevStatsCached = null;
+ }
+
+ private void maybeUpgradeLegacyStatsLocked() {
+ File file;
+ try {
+ file = new File(mSystemDir, "netstats.bin");
+ if (file.exists()) {
+ mDevRecorder.importLegacyNetworkLocked(file);
+ file.delete();
+ }
+
+ file = new File(mSystemDir, "netstats_xt.bin");
+ if (file.exists()) {
+ file.delete();
+ }
+
+ file = new File(mSystemDir, "netstats_uid.bin");
+ if (file.exists()) {
+ mUidRecorder.importLegacyUidLocked(file);
+ mUidTagRecorder.importLegacyUidLocked(file);
+ file.delete();
+ }
+ } catch (IOException e) {
+ Log.wtf(TAG, "problem during legacy upgrade", e);
}
- if (mUidStatsLoaded) {
- writeUidStatsLocked();
- }
- mNetworkDevStats.clear();
- mNetworkXtStats.clear();
- mUidStats.clear();
- mNetworkStatsLoaded = false;
- mUidStatsLoaded = false;
}
/**
@@ -372,7 +394,7 @@
*/
private void registerGlobalAlert() {
try {
- final long alertBytes = mSettings.getPersistThreshold();
+ final long alertBytes = mSettings.getGlobalAlertBytes();
mNetworkManager.setGlobalAlert(alertBytes);
} catch (IllegalStateException e) {
Slog.w(TAG, "problem registering for global alert: " + e);
@@ -383,161 +405,39 @@
@Override
public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- return getHistoryForNetworkDev(template, fields);
+ return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields);
}
- private NetworkStatsHistory getHistoryForNetworkDev(NetworkTemplate template, int fields) {
- return getHistoryForNetwork(template, fields, mNetworkDevStats);
- }
-
- private NetworkStatsHistory getHistoryForNetworkXt(NetworkTemplate template, int fields) {
- return getHistoryForNetwork(template, fields, mNetworkXtStats);
- }
-
- private NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields,
- HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
- synchronized (mStatsLock) {
- // combine all interfaces that match template
- final NetworkStatsHistory combined = new NetworkStatsHistory(
- mSettings.getNetworkBucketDuration(), estimateNetworkBuckets(), fields);
- for (NetworkIdentitySet ident : source.keySet()) {
- if (templateMatches(template, ident)) {
- final NetworkStatsHistory history = source.get(ident);
- if (history != null) {
- combined.recordEntireHistory(history);
- }
- }
- }
- return combined;
- }
+ @Override
+ public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) {
+ return mDevStatsCached.getSummary(template, start, end);
}
@Override
public NetworkStatsHistory getHistoryForUid(
NetworkTemplate template, int uid, int set, int tag, int fields) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-
- synchronized (mStatsLock) {
- ensureUidStatsLoadedLocked();
-
- // combine all interfaces that match template
- final NetworkStatsHistory combined = new NetworkStatsHistory(
- mSettings.getUidBucketDuration(), estimateUidBuckets(), fields);
- for (UidStatsKey key : mUidStats.keySet()) {
- final boolean setMatches = set == SET_ALL || key.set == set;
- if (templateMatches(template, key.ident) && key.uid == uid && setMatches
- && key.tag == tag) {
- final NetworkStatsHistory history = mUidStats.get(key);
- combined.recordEntireHistory(history);
- }
- }
-
- return combined;
+ // TODO: transition to stats sessions to avoid WeakReferences
+ if (tag == TAG_NONE) {
+ return mUidRecorder.getOrLoadCompleteLocked().getHistory(
+ template, uid, set, tag, fields);
+ } else {
+ return mUidTagRecorder.getOrLoadCompleteLocked().getHistory(
+ template, uid, set, tag, fields);
}
}
@Override
- public NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
- return getSummaryForNetworkDev(template, start, end);
- }
-
- private NetworkStats getSummaryForNetworkDev(NetworkTemplate template, long start, long end) {
- return getSummaryForNetwork(template, start, end, mNetworkDevStats);
- }
-
- private NetworkStats getSummaryForNetworkXt(NetworkTemplate template, long start, long end) {
- return getSummaryForNetwork(template, start, end, mNetworkXtStats);
- }
-
- private NetworkStats getSummaryForNetwork(NetworkTemplate template, long start, long end,
- HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
- synchronized (mStatsLock) {
- // use system clock to be externally consistent
- final long now = System.currentTimeMillis();
-
- final NetworkStats stats = new NetworkStats(end - start, 1);
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- NetworkStatsHistory.Entry historyEntry = null;
-
- // combine total from all interfaces that match template
- for (NetworkIdentitySet ident : source.keySet()) {
- if (templateMatches(template, ident)) {
- final NetworkStatsHistory history = source.get(ident);
- historyEntry = history.getValues(start, end, now, historyEntry);
-
- entry.iface = IFACE_ALL;
- entry.uid = UID_ALL;
- entry.tag = TAG_NONE;
- entry.rxBytes = historyEntry.rxBytes;
- entry.rxPackets = historyEntry.rxPackets;
- entry.txBytes = historyEntry.txBytes;
- entry.txPackets = historyEntry.txPackets;
-
- stats.combineValues(entry);
- }
- }
-
- return stats;
- }
- }
-
- private long getHistoryStartLocked(
- NetworkTemplate template, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
- long start = Long.MAX_VALUE;
- for (NetworkIdentitySet ident : source.keySet()) {
- if (templateMatches(template, ident)) {
- final NetworkStatsHistory history = source.get(ident);
- start = Math.min(start, history.getStart());
- }
- }
- return start;
- }
-
- @Override
public NetworkStats getSummaryForAllUid(
NetworkTemplate template, long start, long end, boolean includeTags) {
- mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
-
- synchronized (mStatsLock) {
- ensureUidStatsLoadedLocked();
-
- // use system clock to be externally consistent
- final long now = System.currentTimeMillis();
-
- final NetworkStats stats = new NetworkStats(end - start, 24);
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- NetworkStatsHistory.Entry historyEntry = null;
-
- for (UidStatsKey key : mUidStats.keySet()) {
- if (templateMatches(template, key.ident)) {
- // always include summary under TAG_NONE, and include
- // other tags when requested.
- if (key.tag == TAG_NONE || includeTags) {
- final NetworkStatsHistory history = mUidStats.get(key);
- historyEntry = history.getValues(start, end, now, historyEntry);
-
- entry.iface = IFACE_ALL;
- entry.uid = key.uid;
- entry.set = key.set;
- entry.tag = key.tag;
- entry.rxBytes = historyEntry.rxBytes;
- entry.rxPackets = historyEntry.rxPackets;
- entry.txBytes = historyEntry.txBytes;
- entry.txPackets = historyEntry.txPackets;
- entry.operations = historyEntry.operations;
-
- if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
- || entry.txPackets > 0 || entry.operations > 0) {
- stats.combineValues(entry);
- }
- }
- }
- }
-
- return stats;
+ // TODO: transition to stats sessions to avoid WeakReferences
+ final NetworkStats stats = mUidRecorder.getOrLoadCompleteLocked().getSummary(
+ template, start, end);
+ if (includeTags) {
+ final NetworkStats tagStats = mUidTagRecorder.getOrLoadCompleteLocked().getSummary(
+ template, start, end);
+ stats.combineAllValues(tagStats);
}
+ return stats;
}
@Override
@@ -567,7 +467,7 @@
}
// splice in operation counts
- dataLayer.spliceOperationsFrom(mOperations);
+ dataLayer.spliceOperationsFrom(mUidOperations);
return dataLayer;
}
@@ -586,8 +486,10 @@
synchronized (mStatsLock) {
final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT);
- mOperations.combineValues(IFACE_ALL, uid, set, tag, 0L, 0L, 0L, 0L, operationCount);
- mOperations.combineValues(IFACE_ALL, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount);
+ mUidOperations.combineValues(
+ mActiveIface, uid, set, tag, 0L, 0L, 0L, 0L, operationCount);
+ mUidOperations.combineValues(
+ mActiveIface, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount);
}
}
@@ -755,13 +657,17 @@
performPollLocked(FLAG_PERSIST_NETWORK);
final NetworkState[] states;
+ final LinkProperties activeLink;
try {
states = mConnManager.getAllNetworkState();
+ activeLink = mConnManager.getActiveLinkProperties();
} catch (RemoteException e) {
// ignored; service lives in system_server
return;
}
+ mActiveIface = activeLink != null ? activeLink.getInterfaceName() : null;
+
// rebuild active interfaces based on connected networks
mActiveIfaces.clear();
@@ -785,12 +691,20 @@
* Bootstrap initial stats snapshot, usually during {@link #systemReady()}
* so we have baseline values without double-counting.
*/
- private void bootstrapStats() {
+ private void bootstrapStatsLocked() {
+ final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+ : System.currentTimeMillis();
+
try {
- mLastPollUidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
- mLastPollNetworkDevSnapshot = mNetworkManager.getNetworkStatsSummary();
- mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot);
- mLastPollOperationsSnapshot = new NetworkStats(0L, 0);
+ // snapshot and record current counters; read UID stats first to
+ // avoid overcounting dev stats.
+ final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
+ final NetworkStats devSnapshot = getNetworkStatsSummary();
+
+ mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
+ mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+ mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+
} catch (IllegalStateException e) {
Slog.w(TAG, "problem reading network stats: " + e);
} catch (RemoteException e) {
@@ -830,27 +744,16 @@
// TODO: consider marking "untrusted" times in historical stats
final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
: System.currentTimeMillis();
- final long threshold = mSettings.getPersistThreshold();
- final NetworkStats uidSnapshot;
- final NetworkStats networkXtSnapshot;
- final NetworkStats networkDevSnapshot;
try {
- // collect any tethering stats
- final NetworkStats tetherSnapshot = getNetworkStatsTethering();
+ // snapshot and record current counters; read UID stats first to
+ // avoid overcounting dev stats.
+ final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
+ final NetworkStats devSnapshot = getNetworkStatsSummary();
- // record uid stats, folding in tethering stats
- uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
- uidSnapshot.combineAllValues(tetherSnapshot);
- performUidPollLocked(uidSnapshot, currentTime);
-
- // record dev network stats
- networkDevSnapshot = mNetworkManager.getNetworkStatsSummary();
- performNetworkDevPollLocked(networkDevSnapshot, currentTime);
-
- // record xt network stats
- networkXtSnapshot = computeNetworkXtSnapshotFromUid(uidSnapshot);
- performNetworkXtPollLocked(networkXtSnapshot, currentTime);
+ mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
+ mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+ mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem reading network stats", e);
@@ -860,26 +763,19 @@
return;
}
- // persist when enough network data has occurred
- final long persistNetworkDevDelta = computeStatsDelta(
- mLastPersistNetworkDevSnapshot, networkDevSnapshot, true, "devp").getTotalBytes();
- final long persistNetworkXtDelta = computeStatsDelta(
- mLastPersistNetworkXtSnapshot, networkXtSnapshot, true, "xtp").getTotalBytes();
- final boolean networkOverThreshold = persistNetworkDevDelta > threshold
- || persistNetworkXtDelta > threshold;
- if (persistForce || (persistNetwork && networkOverThreshold)) {
- writeNetworkDevStatsLocked();
- writeNetworkXtStatsLocked();
- mLastPersistNetworkDevSnapshot = networkDevSnapshot;
- mLastPersistNetworkXtSnapshot = networkXtSnapshot;
- }
-
- // persist when enough uid data has occurred
- final long persistUidDelta = computeStatsDelta(
- mLastPersistUidSnapshot, uidSnapshot, true, "uidp").getTotalBytes();
- if (persistForce || (persistUid && persistUidDelta > threshold)) {
- writeUidStatsLocked();
- mLastPersistUidSnapshot = uidSnapshot;
+ // persist any pending data depending on requested flags
+ if (persistForce) {
+ mDevRecorder.forcePersistLocked(currentTime);
+ mUidRecorder.forcePersistLocked(currentTime);
+ mUidTagRecorder.forcePersistLocked(currentTime);
+ } else {
+ if (persistNetwork) {
+ mDevRecorder.maybePersistLocked(currentTime);
+ }
+ if (persistUid) {
+ mUidRecorder.maybePersistLocked(currentTime);
+ mUidTagRecorder.maybePersistLocked(currentTime);
+ }
}
if (LOGV) {
@@ -887,9 +783,9 @@
Slog.v(TAG, "performPollLocked() took " + duration + "ms");
}
- if (ENABLE_SAMPLE_AFTER_POLL) {
+ if (mSettings.getSampleEnabled()) {
// sample stats after each full poll
- performSample();
+ performSampleLocked();
}
// finally, dispatch updated event to any listeners
@@ -899,511 +795,58 @@
}
/**
- * Update {@link #mNetworkDevStats} historical usage.
- */
- private void performNetworkDevPollLocked(NetworkStats networkDevSnapshot, long currentTime) {
- final HashSet<String> unknownIface = Sets.newHashSet();
-
- final NetworkStats delta = computeStatsDelta(
- mLastPollNetworkDevSnapshot, networkDevSnapshot, false, "dev");
- final long timeStart = currentTime - delta.getElapsedRealtime();
-
- NetworkStats.Entry entry = null;
- for (int i = 0; i < delta.size(); i++) {
- entry = delta.getValues(i, entry);
- final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
- if (ident == null) {
- unknownIface.add(entry.iface);
- continue;
- }
-
- final NetworkStatsHistory history = findOrCreateNetworkDevStatsLocked(ident);
- history.recordData(timeStart, currentTime, entry);
- }
-
- mLastPollNetworkDevSnapshot = networkDevSnapshot;
-
- if (LOGD && unknownIface.size() > 0) {
- Slog.w(TAG, "unknown dev interfaces " + unknownIface + ", ignoring those stats");
- }
- }
-
- /**
- * Update {@link #mNetworkXtStats} historical usage.
- */
- private void performNetworkXtPollLocked(NetworkStats networkXtSnapshot, long currentTime) {
- final HashSet<String> unknownIface = Sets.newHashSet();
-
- final NetworkStats delta = computeStatsDelta(
- mLastPollNetworkXtSnapshot, networkXtSnapshot, false, "xt");
- final long timeStart = currentTime - delta.getElapsedRealtime();
-
- NetworkStats.Entry entry = null;
- for (int i = 0; i < delta.size(); i++) {
- entry = delta.getValues(i, entry);
- final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
- if (ident == null) {
- unknownIface.add(entry.iface);
- continue;
- }
-
- final NetworkStatsHistory history = findOrCreateNetworkXtStatsLocked(ident);
- history.recordData(timeStart, currentTime, entry);
- }
-
- mLastPollNetworkXtSnapshot = networkXtSnapshot;
-
- if (LOGD && unknownIface.size() > 0) {
- Slog.w(TAG, "unknown xt interfaces " + unknownIface + ", ignoring those stats");
- }
- }
-
- /**
- * Update {@link #mUidStats} historical usage.
- */
- private void performUidPollLocked(NetworkStats uidSnapshot, long currentTime) {
- ensureUidStatsLoadedLocked();
-
- final NetworkStats delta = computeStatsDelta(
- mLastPollUidSnapshot, uidSnapshot, false, "uid");
- final NetworkStats operationsDelta = computeStatsDelta(
- mLastPollOperationsSnapshot, mOperations, false, "uidop");
- final long timeStart = currentTime - delta.getElapsedRealtime();
-
- NetworkStats.Entry entry = null;
- NetworkStats.Entry operationsEntry = null;
- for (int i = 0; i < delta.size(); i++) {
- entry = delta.getValues(i, entry);
- final NetworkIdentitySet ident = mActiveIfaces.get(entry.iface);
- if (ident == null) {
- if (entry.rxBytes > 0 || entry.rxPackets > 0 || entry.txBytes > 0
- || entry.txPackets > 0) {
- Log.w(TAG, "dropping UID delta from unknown iface: " + entry);
- }
- continue;
- }
-
- // splice in operation counts since last poll
- final int j = operationsDelta.findIndex(IFACE_ALL, entry.uid, entry.set, entry.tag);
- if (j != -1) {
- operationsEntry = operationsDelta.getValues(j, operationsEntry);
- entry.operations = operationsEntry.operations;
- }
-
- final NetworkStatsHistory history = findOrCreateUidStatsLocked(
- ident, entry.uid, entry.set, entry.tag);
- history.recordData(timeStart, currentTime, entry);
- }
-
- mLastPollUidSnapshot = uidSnapshot;
- mLastPollOperationsSnapshot = mOperations.clone();
- }
-
- /**
* Sample recent statistics summary into {@link EventLog}.
*/
- private void performSample() {
- final long largestBucketSize = Math.max(
- mSettings.getNetworkBucketDuration(), mSettings.getUidBucketDuration());
-
- // take sample as atomic buckets
- final long now = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
- final long end = now - (now % largestBucketSize) + largestBucketSize;
- final long start = end - largestBucketSize;
-
+ private void performSampleLocked() {
+ // TODO: migrate trustedtime fixes to separate binary log events
final long trustedTime = mTime.hasCache() ? mTime.currentTimeMillis() : -1;
- long devHistoryStart = Long.MAX_VALUE;
- NetworkTemplate template = null;
- NetworkStats.Entry devTotal = null;
- NetworkStats.Entry xtTotal = null;
- NetworkStats.Entry uidTotal = null;
+ NetworkTemplate template;
+ NetworkStats.Entry devTotal;
+ NetworkStats.Entry xtTotal;
+ NetworkStats.Entry uidTotal;
// collect mobile sample
template = buildTemplateMobileAll(getActiveSubscriberId(mContext));
- devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal);
- devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats);
- xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal);
- uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
+ devTotal = mDevRecorder.getTotalSinceBootLocked(template);
+ xtTotal = new NetworkStats.Entry();
+ uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
EventLogTags.writeNetstatsMobileSample(
devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
- trustedTime, devHistoryStart);
+ trustedTime);
// collect wifi sample
template = buildTemplateWifi();
- devTotal = getSummaryForNetworkDev(template, start, end).getTotal(devTotal);
- devHistoryStart = getHistoryStartLocked(template, mNetworkDevStats);
- xtTotal = getSummaryForNetworkXt(template, start, end).getTotal(xtTotal);
- uidTotal = getSummaryForAllUid(template, start, end, false).getTotal(uidTotal);
+ devTotal = mDevRecorder.getTotalSinceBootLocked(template);
+ xtTotal = new NetworkStats.Entry();
+ uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
+
EventLogTags.writeNetstatsWifiSample(
devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
- trustedTime, devHistoryStart);
+ trustedTime);
}
/**
- * Clean up {@link #mUidStats} after UID is removed.
+ * Clean up {@link #mUidRecorder} after UID is removed.
*/
private void removeUidLocked(int uid) {
- ensureUidStatsLoadedLocked();
-
// perform one last poll before removing
performPollLocked(FLAG_PERSIST_ALL);
- final ArrayList<UidStatsKey> knownKeys = Lists.newArrayList();
- knownKeys.addAll(mUidStats.keySet());
-
- // migrate all UID stats into special "removed" bucket
- for (UidStatsKey key : knownKeys) {
- if (key.uid == uid) {
- // only migrate combined TAG_NONE history
- if (key.tag == TAG_NONE) {
- final NetworkStatsHistory uidHistory = mUidStats.get(key);
- final NetworkStatsHistory removedHistory = findOrCreateUidStatsLocked(
- key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
- removedHistory.recordEntireHistory(uidHistory);
- }
- mUidStats.remove(key);
- }
- }
-
- // clear UID from current stats snapshot
- if (mLastPollUidSnapshot != null) {
- mLastPollUidSnapshot = mLastPollUidSnapshot.withoutUid(uid);
- mLastPollNetworkXtSnapshot = computeNetworkXtSnapshotFromUid(mLastPollUidSnapshot);
- }
+ mUidRecorder.removeUidLocked(uid);
+ mUidTagRecorder.removeUidLocked(uid);
// clear kernel stats associated with UID
resetKernelUidStats(uid);
-
- // since this was radical rewrite, push to disk
- writeUidStatsLocked();
- }
-
- private NetworkStatsHistory findOrCreateNetworkXtStatsLocked(NetworkIdentitySet ident) {
- return findOrCreateNetworkStatsLocked(ident, mNetworkXtStats);
- }
-
- private NetworkStatsHistory findOrCreateNetworkDevStatsLocked(NetworkIdentitySet ident) {
- return findOrCreateNetworkStatsLocked(ident, mNetworkDevStats);
- }
-
- private NetworkStatsHistory findOrCreateNetworkStatsLocked(
- NetworkIdentitySet ident, HashMap<NetworkIdentitySet, NetworkStatsHistory> source) {
- final NetworkStatsHistory existing = source.get(ident);
-
- // update when no existing, or when bucket duration changed
- final long bucketDuration = mSettings.getNetworkBucketDuration();
- NetworkStatsHistory updated = null;
- if (existing == null) {
- updated = new NetworkStatsHistory(bucketDuration, 10);
- } else if (existing.getBucketDuration() != bucketDuration) {
- updated = new NetworkStatsHistory(
- bucketDuration, estimateResizeBuckets(existing, bucketDuration));
- updated.recordEntireHistory(existing);
- }
-
- if (updated != null) {
- source.put(ident, updated);
- return updated;
- } else {
- return existing;
- }
- }
-
- private NetworkStatsHistory findOrCreateUidStatsLocked(
- NetworkIdentitySet ident, int uid, int set, int tag) {
- ensureUidStatsLoadedLocked();
-
- final UidStatsKey key = new UidStatsKey(ident, uid, set, tag);
- final NetworkStatsHistory existing = mUidStats.get(key);
-
- // update when no existing, or when bucket duration changed
- final long bucketDuration = mSettings.getUidBucketDuration();
- NetworkStatsHistory updated = null;
- if (existing == null) {
- updated = new NetworkStatsHistory(bucketDuration, 10);
- } else if (existing.getBucketDuration() != bucketDuration) {
- updated = new NetworkStatsHistory(
- bucketDuration, estimateResizeBuckets(existing, bucketDuration));
- updated.recordEntireHistory(existing);
- }
-
- if (updated != null) {
- mUidStats.put(key, updated);
- return updated;
- } else {
- return existing;
- }
- }
-
- private void readNetworkDevStatsLocked() {
- if (LOGV) Slog.v(TAG, "readNetworkDevStatsLocked()");
- readNetworkStats(mNetworkDevFile, mNetworkDevStats);
- }
-
- private void readNetworkXtStatsLocked() {
- if (LOGV) Slog.v(TAG, "readNetworkXtStatsLocked()");
- readNetworkStats(mNetworkXtFile, mNetworkXtStats);
- }
-
- private static void readNetworkStats(
- AtomicFile inputFile, HashMap<NetworkIdentitySet, NetworkStatsHistory> output) {
- // clear any existing stats and read from disk
- output.clear();
-
- DataInputStream in = null;
- try {
- in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
-
- // verify file magic header intact
- final int magic = in.readInt();
- if (magic != FILE_MAGIC) {
- throw new ProtocolException("unexpected magic: " + magic);
- }
-
- final int version = in.readInt();
- switch (version) {
- case VERSION_NETWORK_INIT: {
- // network := size *(NetworkIdentitySet NetworkStatsHistory)
- final int size = in.readInt();
- for (int i = 0; i < size; i++) {
- final NetworkIdentitySet ident = new NetworkIdentitySet(in);
- final NetworkStatsHistory history = new NetworkStatsHistory(in);
- output.put(ident, history);
- }
- break;
- }
- default: {
- throw new ProtocolException("unexpected version: " + version);
- }
- }
- } catch (FileNotFoundException e) {
- // missing stats is okay, probably first boot
- } catch (IOException e) {
- Log.wtf(TAG, "problem reading network stats", e);
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
-
- private void ensureUidStatsLoadedLocked() {
- if (!mUidStatsLoaded) {
- readUidStatsLocked();
- mUidStatsLoaded = true;
- }
- }
-
- private void readUidStatsLocked() {
- if (LOGV) Slog.v(TAG, "readUidStatsLocked()");
-
- // clear any existing stats and read from disk
- mUidStats.clear();
-
- DataInputStream in = null;
- try {
- in = new DataInputStream(new BufferedInputStream(mUidFile.openRead()));
-
- // verify file magic header intact
- final int magic = in.readInt();
- if (magic != FILE_MAGIC) {
- throw new ProtocolException("unexpected magic: " + magic);
- }
-
- final int version = in.readInt();
- switch (version) {
- case VERSION_UID_INIT: {
- // uid := size *(UID NetworkStatsHistory)
-
- // drop this data version, since we don't have a good
- // mapping into NetworkIdentitySet.
- break;
- }
- case VERSION_UID_WITH_IDENT: {
- // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
-
- // drop this data version, since this version only existed
- // for a short time.
- break;
- }
- case VERSION_UID_WITH_TAG:
- case VERSION_UID_WITH_SET: {
- // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
- final int identSize = in.readInt();
- for (int i = 0; i < identSize; i++) {
- final NetworkIdentitySet ident = new NetworkIdentitySet(in);
-
- final int size = in.readInt();
- for (int j = 0; j < size; j++) {
- final int uid = in.readInt();
- final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
- : SET_DEFAULT;
- final int tag = in.readInt();
-
- final UidStatsKey key = new UidStatsKey(ident, uid, set, tag);
- final NetworkStatsHistory history = new NetworkStatsHistory(in);
- mUidStats.put(key, history);
- }
- }
- break;
- }
- default: {
- throw new ProtocolException("unexpected version: " + version);
- }
- }
- } catch (FileNotFoundException e) {
- // missing stats is okay, probably first boot
- } catch (IOException e) {
- Log.wtf(TAG, "problem reading uid stats", e);
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
-
- private void writeNetworkDevStatsLocked() {
- if (LOGV) Slog.v(TAG, "writeNetworkDevStatsLocked()");
- writeNetworkStats(mNetworkDevStats, mNetworkDevFile);
- }
-
- private void writeNetworkXtStatsLocked() {
- if (LOGV) Slog.v(TAG, "writeNetworkXtStatsLocked()");
- writeNetworkStats(mNetworkXtStats, mNetworkXtFile);
- }
-
- private void writeNetworkStats(
- HashMap<NetworkIdentitySet, NetworkStatsHistory> input, AtomicFile outputFile) {
- // TODO: consider duplicating stats and releasing lock while writing
-
- // trim any history beyond max
- if (mTime.hasCache()) {
- final long systemCurrentTime = System.currentTimeMillis();
- final long trustedCurrentTime = mTime.currentTimeMillis();
-
- final long currentTime = Math.min(systemCurrentTime, trustedCurrentTime);
- final long maxHistory = mSettings.getNetworkMaxHistory();
-
- for (NetworkStatsHistory history : input.values()) {
- final int beforeSize = history.size();
- history.removeBucketsBefore(currentTime - maxHistory);
- final int afterSize = history.size();
-
- if (beforeSize > 24 && afterSize < beforeSize / 2) {
- // yikes, dropping more than half of significant history
- final StringBuilder builder = new StringBuilder();
- builder.append("yikes, dropping more than half of history").append('\n');
- builder.append("systemCurrentTime=").append(systemCurrentTime).append('\n');
- builder.append("trustedCurrentTime=").append(trustedCurrentTime).append('\n');
- builder.append("maxHistory=").append(maxHistory).append('\n');
- builder.append("beforeSize=").append(beforeSize).append('\n');
- builder.append("afterSize=").append(afterSize).append('\n');
- mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
- }
- }
- }
-
- FileOutputStream fos = null;
- try {
- fos = outputFile.startWrite();
- final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos));
-
- out.writeInt(FILE_MAGIC);
- out.writeInt(VERSION_NETWORK_INIT);
-
- out.writeInt(input.size());
- for (NetworkIdentitySet ident : input.keySet()) {
- final NetworkStatsHistory history = input.get(ident);
- ident.writeToStream(out);
- history.writeToStream(out);
- }
-
- out.flush();
- outputFile.finishWrite(fos);
- } catch (IOException e) {
- Log.wtf(TAG, "problem writing stats", e);
- if (fos != null) {
- outputFile.failWrite(fos);
- }
- }
- }
-
- private void writeUidStatsLocked() {
- if (LOGV) Slog.v(TAG, "writeUidStatsLocked()");
-
- if (!mUidStatsLoaded) {
- Slog.w(TAG, "asked to write UID stats when not loaded; skipping");
- return;
- }
-
- // TODO: consider duplicating stats and releasing lock while writing
-
- // trim any history beyond max
- if (mTime.hasCache()) {
- final long currentTime = Math.min(
- System.currentTimeMillis(), mTime.currentTimeMillis());
- final long maxUidHistory = mSettings.getUidMaxHistory();
- final long maxTagHistory = mSettings.getTagMaxHistory();
- for (UidStatsKey key : mUidStats.keySet()) {
- final NetworkStatsHistory history = mUidStats.get(key);
-
- // detailed tags are trimmed sooner than summary in TAG_NONE
- if (key.tag == TAG_NONE) {
- history.removeBucketsBefore(currentTime - maxUidHistory);
- } else {
- history.removeBucketsBefore(currentTime - maxTagHistory);
- }
- }
- }
-
- // build UidStatsKey lists grouped by ident
- final HashMap<NetworkIdentitySet, ArrayList<UidStatsKey>> keysByIdent = Maps.newHashMap();
- for (UidStatsKey key : mUidStats.keySet()) {
- ArrayList<UidStatsKey> keys = keysByIdent.get(key.ident);
- if (keys == null) {
- keys = Lists.newArrayList();
- keysByIdent.put(key.ident, keys);
- }
- keys.add(key);
- }
-
- FileOutputStream fos = null;
- try {
- fos = mUidFile.startWrite();
- final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos));
-
- out.writeInt(FILE_MAGIC);
- out.writeInt(VERSION_UID_WITH_SET);
-
- out.writeInt(keysByIdent.size());
- for (NetworkIdentitySet ident : keysByIdent.keySet()) {
- final ArrayList<UidStatsKey> keys = keysByIdent.get(ident);
- ident.writeToStream(out);
-
- out.writeInt(keys.size());
- for (UidStatsKey key : keys) {
- final NetworkStatsHistory history = mUidStats.get(key);
- out.writeInt(key.uid);
- out.writeInt(key.set);
- out.writeInt(key.tag);
- history.writeToStream(out);
- }
- }
-
- out.flush();
- mUidFile.finishWrite(fos);
- } catch (IOException e) {
- Log.wtf(TAG, "problem writing stats", e);
- if (fos != null) {
- mUidFile.failWrite(fos);
- }
- }
}
@Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
final HashSet<String> argSet = new HashSet<String>();
@@ -1411,187 +854,68 @@
argSet.add(arg);
}
- final boolean fullHistory = argSet.contains("full");
+ // usage: dumpsys netstats --full --uid --tag
+ final boolean poll = argSet.contains("--poll") || argSet.contains("poll");
+ final boolean fullHistory = argSet.contains("--full") || argSet.contains("full");
+ final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail");
+ final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail");
+
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
synchronized (mStatsLock) {
- // TODO: remove this testing code, since it corrupts stats
- if (argSet.contains("generate")) {
- generateRandomLocked(args);
- pw.println("Generated stub stats");
- return;
- }
-
- if (argSet.contains("poll")) {
+ if (poll) {
performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
pw.println("Forced poll");
return;
}
pw.println("Active interfaces:");
+ pw.increaseIndent();
for (String iface : mActiveIfaces.keySet()) {
final NetworkIdentitySet ident = mActiveIfaces.get(iface);
- pw.print(" iface="); pw.print(iface);
+ pw.print("iface="); pw.print(iface);
pw.print(" ident="); pw.println(ident.toString());
}
+ pw.decreaseIndent();
- pw.println("Known historical dev stats:");
- for (NetworkIdentitySet ident : mNetworkDevStats.keySet()) {
- final NetworkStatsHistory history = mNetworkDevStats.get(ident);
- pw.print(" ident="); pw.println(ident.toString());
- history.dump(" ", pw, fullHistory);
+ pw.println("Dev stats:");
+ pw.increaseIndent();
+ mDevRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
+
+ if (includeUid) {
+ pw.println("UID stats:");
+ pw.increaseIndent();
+ mUidRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
}
- pw.println("Known historical xt stats:");
- for (NetworkIdentitySet ident : mNetworkXtStats.keySet()) {
- final NetworkStatsHistory history = mNetworkXtStats.get(ident);
- pw.print(" ident="); pw.println(ident.toString());
- history.dump(" ", pw, fullHistory);
- }
-
- if (argSet.contains("detail")) {
- // since explicitly requested with argument, we're okay to load
- // from disk if not already in memory.
- ensureUidStatsLoadedLocked();
-
- final ArrayList<UidStatsKey> keys = Lists.newArrayList();
- keys.addAll(mUidStats.keySet());
- Collections.sort(keys);
-
- pw.println("Detailed UID stats:");
- for (UidStatsKey key : keys) {
- pw.print(" ident="); pw.print(key.ident.toString());
- pw.print(" uid="); pw.print(key.uid);
- pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
- pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
-
- final NetworkStatsHistory history = mUidStats.get(key);
- history.dump(" ", pw, fullHistory);
- }
+ if (includeTag) {
+ pw.println("UID tag stats:");
+ pw.increaseIndent();
+ mUidTagRecorder.dumpLocked(pw, fullHistory);
+ pw.decreaseIndent();
}
}
}
+ private NetworkStats getNetworkStatsSummary() throws RemoteException {
+ return mNetworkManager.getNetworkStatsSummary();
+ }
+
/**
- * @deprecated only for temporary testing
+ * Return snapshot of current UID statistics, including any
+ * {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values.
*/
- @Deprecated
- private void generateRandomLocked(String[] args) {
- final long totalBytes = Long.parseLong(args[1]);
- final long totalTime = Long.parseLong(args[2]);
-
- final PackageManager pm = mContext.getPackageManager();
- final ArrayList<Integer> specialUidList = Lists.newArrayList();
- for (int i = 3; i < args.length; i++) {
- try {
- specialUidList.add(pm.getApplicationInfo(args[i], 0).uid);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
+ private NetworkStats getNetworkStatsUidDetail() throws RemoteException {
+ final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
- final HashSet<Integer> otherUidSet = Sets.newHashSet();
- for (ApplicationInfo info : pm.getInstalledApplications(0)) {
- if (pm.checkPermission(android.Manifest.permission.INTERNET, info.packageName)
- == PackageManager.PERMISSION_GRANTED && !specialUidList.contains(info.uid)) {
- otherUidSet.add(info.uid);
- }
- }
+ // fold tethering stats and operations into uid snapshot
+ final NetworkStats tetherSnapshot = getNetworkStatsTethering();
+ uidSnapshot.combineAllValues(tetherSnapshot);
+ uidSnapshot.combineAllValues(mUidOperations);
- final ArrayList<Integer> otherUidList = new ArrayList<Integer>(otherUidSet);
-
- final long end = System.currentTimeMillis();
- final long start = end - totalTime;
-
- mNetworkDevStats.clear();
- mNetworkXtStats.clear();
- mUidStats.clear();
-
- final Random r = new Random();
- for (NetworkIdentitySet ident : mActiveIfaces.values()) {
- final NetworkStatsHistory devHistory = findOrCreateNetworkDevStatsLocked(ident);
- final NetworkStatsHistory xtHistory = findOrCreateNetworkXtStatsLocked(ident);
-
- final ArrayList<Integer> uidList = new ArrayList<Integer>();
- uidList.addAll(specialUidList);
-
- if (uidList.size() == 0) {
- Collections.shuffle(otherUidList);
- uidList.addAll(otherUidList);
- }
-
- boolean first = true;
- long remainingBytes = totalBytes;
- for (int uid : uidList) {
- final NetworkStatsHistory defaultHistory = findOrCreateUidStatsLocked(
- ident, uid, SET_DEFAULT, TAG_NONE);
- final NetworkStatsHistory foregroundHistory = findOrCreateUidStatsLocked(
- ident, uid, SET_FOREGROUND, TAG_NONE);
-
- final long uidBytes = totalBytes / uidList.size();
-
- final float fractionDefault = r.nextFloat();
- final long defaultBytes = (long) (uidBytes * fractionDefault);
- final long foregroundBytes = (long) (uidBytes * (1 - fractionDefault));
-
- defaultHistory.generateRandom(start, end, defaultBytes);
- foregroundHistory.generateRandom(start, end, foregroundBytes);
-
- if (first) {
- final long bumpTime = (start + end) / 2;
- defaultHistory.recordData(
- bumpTime, bumpTime + DAY_IN_MILLIS, 200 * MB_IN_BYTES, 0);
- first = false;
- }
-
- devHistory.recordEntireHistory(defaultHistory);
- devHistory.recordEntireHistory(foregroundHistory);
- xtHistory.recordEntireHistory(defaultHistory);
- xtHistory.recordEntireHistory(foregroundHistory);
- }
- }
- }
-
- private StatsObserver mStatsObserver = new StatsObserver();
-
- private class StatsObserver implements NonMonotonicObserver {
- private String mCurrentType;
-
- public void setCurrentType(String type) {
- mCurrentType = type;
- }
-
- /** {@inheritDoc} */
- public void foundNonMonotonic(
- NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
- Log.w(TAG, "found non-monotonic values; saving to dropbox");
-
- // record error for debugging
- final StringBuilder builder = new StringBuilder();
- builder.append("found non-monotonic " + mCurrentType + " values at left[" + leftIndex
- + "] - right[" + rightIndex + "]\n");
- builder.append("left=").append(left).append('\n');
- builder.append("right=").append(right).append('\n');
- mDropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
- }
- }
-
- /**
- * Return the delta between two {@link NetworkStats} snapshots, where {@code
- * before} can be {@code null}.
- */
- private NetworkStats computeStatsDelta(
- NetworkStats before, NetworkStats current, boolean collectStale, String type) {
- if (before != null) {
- mStatsObserver.setCurrentType(type);
- return NetworkStats.subtract(current, before, mStatsObserver);
- } else if (collectStale) {
- // caller is okay collecting stale stats for first call.
- return current;
- } else {
- // this is first snapshot; to prevent from double-counting we only
- // observe traffic occuring between known snapshots.
- return new NetworkStats(0L, 10);
- }
+ return uidSnapshot;
}
/**
@@ -1608,35 +932,6 @@
}
}
- private static NetworkStats computeNetworkXtSnapshotFromUid(NetworkStats uidSnapshot) {
- return uidSnapshot.groupedByIface();
- }
-
- private int estimateNetworkBuckets() {
- return (int) (mSettings.getNetworkMaxHistory() / mSettings.getNetworkBucketDuration());
- }
-
- private int estimateUidBuckets() {
- return (int) (mSettings.getUidMaxHistory() / mSettings.getUidBucketDuration());
- }
-
- private static int estimateResizeBuckets(NetworkStatsHistory existing, long newBucketDuration) {
- return (int) (existing.size() * existing.getBucketDuration() / newBucketDuration);
- }
-
- /**
- * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
- * in the given {@link NetworkIdentitySet}.
- */
- private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
- for (NetworkIdentity ident : identSet) {
- if (template.matches(ident)) {
- return true;
- }
- }
- return false;
- }
-
private Handler.Callback mHandlerCallback = new Handler.Callback() {
/** {@inheritDoc} */
public boolean handleMessage(Message msg) {
@@ -1672,40 +967,22 @@
}
}
- /**
- * Key uniquely identifying a {@link NetworkStatsHistory} for a UID.
- */
- private static class UidStatsKey implements Comparable<UidStatsKey> {
- public final NetworkIdentitySet ident;
- public final int uid;
- public final int set;
- public final int tag;
-
- public UidStatsKey(NetworkIdentitySet ident, int uid, int set, int tag) {
- this.ident = ident;
- this.uid = uid;
- this.set = set;
- this.tag = tag;
- }
-
- @Override
- public int hashCode() {
- return Objects.hashCode(ident, uid, set, tag);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof UidStatsKey) {
- final UidStatsKey key = (UidStatsKey) obj;
- return Objects.equal(ident, key.ident) && uid == key.uid && set == key.set
- && tag == key.tag;
- }
- return false;
- }
-
+ private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {
/** {@inheritDoc} */
- public int compareTo(UidStatsKey another) {
- return Integer.compare(uid, another.uid);
+ public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,
+ int rightIndex, String cookie) {
+ Log.w(TAG, "found non-monotonic values; saving to dropbox");
+
+ // record error for debugging
+ final StringBuilder builder = new StringBuilder();
+ builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex
+ + "] - right[" + rightIndex + "]\n");
+ builder.append("left=").append(left).append('\n');
+ builder.append("right=").append(right).append('\n');
+
+ final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
+ Context.DROPBOX_SERVICE);
+ dropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
}
}
@@ -1731,26 +1008,35 @@
public long getPollInterval() {
return getSecureLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
}
- public long getPersistThreshold() {
- return getSecureLong(NETSTATS_PERSIST_THRESHOLD, 2 * MB_IN_BYTES);
- }
- public long getNetworkBucketDuration() {
- return getSecureLong(NETSTATS_NETWORK_BUCKET_DURATION, HOUR_IN_MILLIS);
- }
- public long getNetworkMaxHistory() {
- return getSecureLong(NETSTATS_NETWORK_MAX_HISTORY, 90 * DAY_IN_MILLIS);
- }
- public long getUidBucketDuration() {
- return getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS);
- }
- public long getUidMaxHistory() {
- return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS);
- }
- public long getTagMaxHistory() {
- return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * DAY_IN_MILLIS);
- }
public long getTimeCacheMaxAge() {
- return DAY_IN_MILLIS;
+ return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS);
+ }
+ public long getGlobalAlertBytes() {
+ return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, 2 * MB_IN_BYTES);
+ }
+ public boolean getSampleEnabled() {
+ return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true);
+ }
+
+ public Config getDevConfig() {
+ return new Config(getSecureLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
+ getSecureLong(NETSTATS_DEV_PERSIST_BYTES, 2 * MB_IN_BYTES),
+ getSecureLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
+ getSecureLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS));
+ }
+
+ public Config getUidConfig() {
+ return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+ getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES),
+ getSecureLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS),
+ getSecureLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS));
+ }
+
+ public Config getUidTagConfig() {
+ return new Config(getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+ getSecureLong(NETSTATS_UID_PERSIST_BYTES, 2 * MB_IN_BYTES),
+ getSecureLong(NETSTATS_UID_ROTATE_AGE, 5 * DAY_IN_MILLIS),
+ getSecureLong(NETSTATS_UID_DELETE_AGE, 15 * DAY_IN_MILLIS));
}
}
}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 6b61c47..090ca64 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -620,11 +620,11 @@
packages = new String[size];
components = new ArrayList[size];
uids = new int[size];
- Iterator<HashMap.Entry<String, ArrayList<String>>>
+ Iterator<Map.Entry<String, ArrayList<String>>>
it = mPendingBroadcasts.entrySet().iterator();
int i = 0;
while (it.hasNext() && i < size) {
- HashMap.Entry<String, ArrayList<String>> ent = it.next();
+ Map.Entry<String, ArrayList<String>> ent = it.next();
packages[i] = ent.getKey();
components[i] = ent.getValue();
PackageSetting ps = mSettings.mPackages.get(ent.getKey());
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 32aa7a4..3616aa2 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -62,6 +62,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.Map;
import libcore.io.IoUtils;
@@ -929,7 +930,7 @@
}
if (mRenamedPackages.size() > 0) {
- for (HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) {
+ for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
serializer.startTag(null, "renamed-package");
serializer.attribute(null, "new", e.getKey());
serializer.attribute(null, "old", e.getValue());
@@ -2144,7 +2145,7 @@
printedSomething = false;
if (mRenamedPackages.size() > 0) {
- for (final HashMap.Entry<String, String> e : mRenamedPackages.entrySet()) {
+ for (final Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
if (packageName != null && !packageName.equals(e.getKey())
&& !packageName.equals(e.getValue())) {
continue;
@@ -2258,4 +2259,4 @@
pw.println("Settings parse messages:");
pw.print(mReadMessages.toString());
}
-}
\ No newline at end of file
+}
diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java
index 8fc9a70..55fb038 100644
--- a/services/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -17,13 +17,8 @@
package com.android.server.wm;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Matrix;
-import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.Slog;
import android.view.Surface;
@@ -34,7 +29,8 @@
class ScreenRotationAnimation {
static final String TAG = "ScreenRotationAnimation";
- static final boolean DEBUG = false;
+ static final boolean DEBUG_STATE = false;
+ static final boolean DEBUG_TRANSFORMS = false;
static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200;
@@ -49,11 +45,51 @@
int mOriginalWidth, mOriginalHeight;
int mCurRotation;
- Animation mExitAnimation;
+ // For all animations, "exit" is for the UI elements that are going
+ // away (that is the snapshot of the old screen), and "enter" is for
+ // the new UI elements that are appearing (that is the active windows
+ // in their final orientation).
+
+ // The starting animation for the exiting and entering elements. This
+ // animation applies a transformation while the rotation is in progress.
+ // It is started immediately, before the new entering UI is ready.
+ Animation mStartExitAnimation;
+ final Transformation mStartExitTransformation = new Transformation();
+ Animation mStartEnterAnimation;
+ final Transformation mStartEnterTransformation = new Transformation();
+
+ // The finishing animation for the exiting and entering elements. This
+ // animation needs to undo the transformation of the starting animation.
+ // It starts running once the new rotation UI elements are ready to be
+ // displayed.
+ Animation mFinishExitAnimation;
+ final Transformation mFinishExitTransformation = new Transformation();
+ Animation mFinishEnterAnimation;
+ final Transformation mFinishEnterTransformation = new Transformation();
+
+ // The current active animation to move from the old to the new rotated
+ // state. Which animation is run here will depend on the old and new
+ // rotations.
+ Animation mRotateExitAnimation;
+ final Transformation mRotateExitTransformation = new Transformation();
+ Animation mRotateEnterAnimation;
+ final Transformation mRotateEnterTransformation = new Transformation();
+
+ // A previously running rotate animation. This will be used if we need
+ // to switch to a new rotation before finishing the previous one.
+ Animation mLastRotateExitAnimation;
+ final Transformation mLastRotateExitTransformation = new Transformation();
+ Animation mLastRotateEnterAnimation;
+ final Transformation mLastRotateEnterTransformation = new Transformation();
+
+ // Complete transformations being applied.
final Transformation mExitTransformation = new Transformation();
- Animation mEnterAnimation;
final Transformation mEnterTransformation = new Transformation();
+
boolean mStarted;
+ boolean mAnimRunning;
+ boolean mFinishAnimReady;
+ long mFinishAnimStartTime;
final Matrix mSnapshotInitialMatrix = new Matrix();
final Matrix mSnapshotFinalMatrix = new Matrix();
@@ -133,7 +169,7 @@
mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
mSurface.setAlpha(alpha);
- if (DEBUG) {
+ if (DEBUG_TRANSFORMS) {
float[] srcPnts = new float[] { 0, 0, mWidth, mHeight };
float[] dstPnts = new float[4];
matrix.mapPoints(dstPnts, srcPnts);
@@ -167,7 +203,7 @@
}
// Must be called while in a transaction.
- public void setRotation(int rotation) {
+ private void setRotation(int rotation) {
mCurRotation = rotation;
// Compute the transformation matrix that must be applied
@@ -176,46 +212,78 @@
int delta = deltaRotation(rotation, mSnapshotRotation);
createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
- if (DEBUG) Slog.v(TAG, "**** ROTATION: " + delta);
+ if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta);
setSnapshotTransform(mSnapshotInitialMatrix, 1.0f);
}
+ // Must be called while in a transaction.
+ public boolean setRotation(int rotation, SurfaceSession session,
+ long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
+ setRotation(rotation);
+ return startAnimation(session, maxAnimationDuration, animationScale,
+ finalWidth, finalHeight, false);
+ }
+
/**
* Returns true if animating.
*/
- public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
- float animationScale, int finalWidth, int finalHeight) {
+ private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
+ float animationScale, int finalWidth, int finalHeight, boolean dismissing) {
if (mSurface == null) {
// Can't do animation.
return false;
}
+ if (mStarted) {
+ return true;
+ }
+
+ mStarted = true;
+
+ boolean firstStart = false;
// Figure out how the screen has moved from the original rotation.
int delta = deltaRotation(mCurRotation, mOriginalRotation);
+ if (mFinishExitAnimation == null && (!dismissing || delta != Surface.ROTATION_0)) {
+ if (DEBUG_STATE) Slog.v(TAG, "Creating start and finish animations");
+ firstStart = true;
+ mStartExitAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_start_exit);
+ mStartEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_start_enter);
+ mFinishExitAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_finish_exit);
+ mFinishEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.screen_rotate_finish_enter);
+ }
+
+ if (DEBUG_STATE) Slog.v(TAG, "Rotation delta: " + delta + " finalWidth="
+ + finalWidth + " finalHeight=" + finalHeight
+ + " origWidth=" + mOriginalWidth + " origHeight=" + mOriginalHeight);
+
switch (delta) {
case Surface.ROTATION_0:
- mExitAnimation = AnimationUtils.loadAnimation(mContext,
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.screen_rotate_0_exit);
- mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.screen_rotate_0_enter);
break;
case Surface.ROTATION_90:
- mExitAnimation = AnimationUtils.loadAnimation(mContext,
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.screen_rotate_plus_90_exit);
- mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.screen_rotate_plus_90_enter);
break;
case Surface.ROTATION_180:
- mExitAnimation = AnimationUtils.loadAnimation(mContext,
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.screen_rotate_180_exit);
- mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.screen_rotate_180_enter);
break;
case Surface.ROTATION_270:
- mExitAnimation = AnimationUtils.loadAnimation(mContext,
+ mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.screen_rotate_minus_90_exit);
- mEnterAnimation = AnimationUtils.loadAnimation(mContext,
+ mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.screen_rotate_minus_90_enter);
break;
}
@@ -224,35 +292,85 @@
// means to allow supplying the last and next size. In this definition
// "%p" is the original (let's call it "previous") size, and "%" is the
// screen's current/new size.
- mEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
- mStarted = false;
+ if (firstStart) {
+ if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations");
+ mStartEnterAnimation.initialize(finalWidth, finalHeight,
+ mOriginalWidth, mOriginalHeight);
+ mStartExitAnimation.initialize(finalWidth, finalHeight,
+ mOriginalWidth, mOriginalHeight);
+ mFinishEnterAnimation.initialize(finalWidth, finalHeight,
+ mOriginalWidth, mOriginalHeight);
+ mFinishExitAnimation.initialize(finalWidth, finalHeight,
+ mOriginalWidth, mOriginalHeight);
+ }
+ mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
+ mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
+ mAnimRunning = false;
+ mFinishAnimReady = false;
+ mFinishAnimStartTime = -1;
- mExitAnimation.restrictDuration(maxAnimationDuration);
- mExitAnimation.scaleCurrentDuration(animationScale);
- mEnterAnimation.restrictDuration(maxAnimationDuration);
- mEnterAnimation.scaleCurrentDuration(animationScale);
+ if (firstStart) {
+ mStartExitAnimation.restrictDuration(maxAnimationDuration);
+ mStartExitAnimation.scaleCurrentDuration(animationScale);
+ mStartEnterAnimation.restrictDuration(maxAnimationDuration);
+ mStartEnterAnimation.scaleCurrentDuration(animationScale);
+ mFinishExitAnimation.restrictDuration(maxAnimationDuration);
+ mFinishExitAnimation.scaleCurrentDuration(animationScale);
+ mFinishEnterAnimation.restrictDuration(maxAnimationDuration);
+ mFinishEnterAnimation.scaleCurrentDuration(animationScale);
+ }
+ mRotateExitAnimation.restrictDuration(maxAnimationDuration);
+ mRotateExitAnimation.scaleCurrentDuration(animationScale);
+ mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
+ mRotateEnterAnimation.scaleCurrentDuration(animationScale);
- if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
- ">>> OPEN TRANSACTION ScreenRotationAnimation.dismiss");
- Surface.openTransaction();
+ if (mBlackFrame == null) {
+ if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+ WindowManagerService.TAG,
+ ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
+ Surface.openTransaction();
- try {
- Rect outer = new Rect(-finalWidth, -finalHeight, finalWidth * 2, finalHeight * 2);
- Rect inner = new Rect(0, 0, finalWidth, finalHeight);
- mBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER);
- } catch (Surface.OutOfResourcesException e) {
- Slog.w(TAG, "Unable to allocate black surface", e);
- } finally {
- Surface.closeTransaction();
- if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
- "<<< CLOSE TRANSACTION ScreenRotationAnimation.dismiss");
+ try {
+ Rect outer = new Rect(-finalWidth*1, -finalHeight*1, finalWidth*2, finalHeight*2);
+ Rect inner = new Rect(0, 0, finalWidth, finalHeight);
+ mBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER);
+ } catch (Surface.OutOfResourcesException e) {
+ Slog.w(TAG, "Unable to allocate black surface", e);
+ } finally {
+ Surface.closeTransaction();
+ if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+ WindowManagerService.TAG,
+ "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
+ }
}
return true;
}
+ /**
+ * Returns true if animating.
+ */
+ public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+ float animationScale, int finalWidth, int finalHeight) {
+ if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
+ if (mSurface == null) {
+ // Can't do animation.
+ return false;
+ }
+ if (!mStarted) {
+ startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
+ true);
+ }
+ if (!mStarted) {
+ return false;
+ }
+ if (DEBUG_STATE) Slog.v(TAG, "Setting mFinishAnimReady = true");
+ mFinishAnimReady = true;
+ return true;
+ }
+
public void kill() {
+ if (DEBUG_STATE) Slog.v(TAG, "Kill!");
if (mSurface != null) {
if (WindowManagerService.SHOW_TRANSACTIONS ||
WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
@@ -262,74 +380,198 @@
}
if (mBlackFrame != null) {
mBlackFrame.kill();
+ mBlackFrame = null;
}
- if (mExitAnimation != null) {
- mExitAnimation.cancel();
- mExitAnimation = null;
+ if (mStartExitAnimation != null) {
+ mStartExitAnimation.cancel();
+ mStartExitAnimation = null;
}
- if (mEnterAnimation != null) {
- mEnterAnimation.cancel();
- mEnterAnimation = null;
+ if (mStartEnterAnimation != null) {
+ mStartEnterAnimation.cancel();
+ mStartEnterAnimation = null;
+ }
+ if (mFinishExitAnimation != null) {
+ mFinishExitAnimation.cancel();
+ mFinishExitAnimation = null;
+ }
+ if (mStartEnterAnimation != null) {
+ mStartEnterAnimation.cancel();
+ mStartEnterAnimation = null;
+ }
+ if (mRotateExitAnimation != null) {
+ mRotateExitAnimation.cancel();
+ mRotateExitAnimation = null;
+ }
+ if (mRotateEnterAnimation != null) {
+ mRotateEnterAnimation.cancel();
+ mRotateEnterAnimation = null;
}
}
public boolean isAnimating() {
- return mEnterAnimation != null || mExitAnimation != null;
+ return mStartEnterAnimation != null || mStartExitAnimation != null
+ && mFinishEnterAnimation != null || mFinishExitAnimation != null
+ && mRotateEnterAnimation != null || mRotateExitAnimation != null;
}
public boolean stepAnimation(long now) {
- if (mEnterAnimation == null && mExitAnimation == null) {
+ if (!isAnimating()) {
+ if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running");
return false;
}
- if (!mStarted) {
- if (mEnterAnimation != null) {
- mEnterAnimation.setStartTime(now);
+ if (!mAnimRunning) {
+ if (DEBUG_STATE) Slog.v(TAG, "Step: starting start, finish, rotate");
+ if (mStartEnterAnimation != null) {
+ mStartEnterAnimation.setStartTime(now);
}
- if (mExitAnimation != null) {
- mExitAnimation.setStartTime(now);
+ if (mStartExitAnimation != null) {
+ mStartExitAnimation.setStartTime(now);
}
- mStarted = true;
+ if (mFinishEnterAnimation != null) {
+ mFinishEnterAnimation.setStartTime(0);
+ }
+ if (mFinishExitAnimation != null) {
+ mFinishExitAnimation.setStartTime(0);
+ }
+ if (mRotateEnterAnimation != null) {
+ mRotateEnterAnimation.setStartTime(now);
+ }
+ if (mRotateExitAnimation != null) {
+ mRotateExitAnimation.setStartTime(now);
+ }
+ mAnimRunning = true;
}
- mExitTransformation.clear();
- boolean moreExit = false;
- if (mExitAnimation != null) {
- moreExit = mExitAnimation.getTransformation(now, mExitTransformation);
- if (DEBUG) Slog.v(TAG, "Stepped exit: " + mExitTransformation);
- if (!moreExit) {
- if (DEBUG) Slog.v(TAG, "Exit animation done!");
- mExitAnimation.cancel();
- mExitAnimation = null;
- mExitTransformation.clear();
- if (mSurface != null) {
- mSurface.hide();
- }
+ if (mFinishAnimReady && mFinishAnimStartTime < 0) {
+ if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready");
+ mFinishAnimStartTime = now;
+ }
+
+ // If the start animation is no longer running, we want to keep its
+ // transformation intact until the finish animation also completes.
+
+ boolean moreStartExit = false;
+ if (mStartExitAnimation != null) {
+ mStartExitTransformation.clear();
+ moreStartExit = mStartExitAnimation.getTransformation(now, mStartExitTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start exit: " + mStartExitTransformation);
+ if (!moreStartExit) {
+ if (DEBUG_STATE) Slog.v(TAG, "Start exit animation done!");
+ mStartExitAnimation.cancel();
+ mStartExitAnimation = null;
}
}
- mEnterTransformation.clear();
- boolean moreEnter = false;
- if (mEnterAnimation != null) {
- moreEnter = mEnterAnimation.getTransformation(now, mEnterTransformation);
- if (!moreEnter) {
- mEnterAnimation.cancel();
- mEnterAnimation = null;
- mEnterTransformation.clear();
- if (mBlackFrame != null) {
- mBlackFrame.hide();
- }
- } else {
- if (mBlackFrame != null) {
- mBlackFrame.setMatrix(mEnterTransformation.getMatrix());
- }
+ boolean moreStartEnter = false;
+ if (mStartEnterAnimation != null) {
+ mStartEnterTransformation.clear();
+ moreStartEnter = mStartEnterAnimation.getTransformation(now, mStartEnterTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start enter: " + mStartEnterTransformation);
+ if (!moreStartEnter) {
+ if (DEBUG_STATE) Slog.v(TAG, "Start enter animation done!");
+ mStartEnterAnimation.cancel();
+ mStartEnterAnimation = null;
+ }
+ }
+
+ long finishNow = mFinishAnimReady ? (now - mFinishAnimStartTime) : 0;
+ if (DEBUG_STATE) Slog.v(TAG, "Step: finishNow=" + finishNow);
+
+ mFinishExitTransformation.clear();
+ boolean moreFinishExit = false;
+ if (mFinishExitAnimation != null) {
+ moreFinishExit = mFinishExitAnimation.getTransformation(finishNow, mFinishExitTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish exit: " + mFinishExitTransformation);
+ if (!moreStartExit && !moreFinishExit) {
+ if (DEBUG_STATE) Slog.v(TAG, "Finish exit animation done, clearing start/finish anims!");
+ mStartExitTransformation.clear();
+ mFinishExitAnimation.cancel();
+ mFinishExitAnimation = null;
+ mFinishExitTransformation.clear();
+ }
+ }
+
+ mFinishEnterTransformation.clear();
+ boolean moreFinishEnter = false;
+ if (mFinishEnterAnimation != null) {
+ moreFinishEnter = mFinishEnterAnimation.getTransformation(finishNow, mFinishEnterTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish enter: " + mFinishEnterTransformation);
+ if (!moreStartEnter && !moreFinishEnter) {
+ if (DEBUG_STATE) Slog.v(TAG, "Finish enter animation done, clearing start/finish anims!");
+ mStartEnterTransformation.clear();
+ mFinishEnterAnimation.cancel();
+ mFinishEnterAnimation = null;
+ mFinishEnterTransformation.clear();
+ }
+ }
+
+ mRotateExitTransformation.clear();
+ boolean moreRotateExit = false;
+ if (mRotateExitAnimation != null) {
+ moreRotateExit = mRotateExitAnimation.getTransformation(now, mRotateExitTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate exit: " + mRotateExitTransformation);
+ }
+
+ if (!moreFinishExit && !moreRotateExit) {
+ if (DEBUG_STATE) Slog.v(TAG, "Rotate exit animation done!");
+ mRotateExitAnimation.cancel();
+ mRotateExitAnimation = null;
+ mRotateExitTransformation.clear();
+ }
+
+ mRotateEnterTransformation.clear();
+ boolean moreRotateEnter = false;
+ if (mRotateEnterAnimation != null) {
+ moreRotateEnter = mRotateEnterAnimation.getTransformation(now, mRotateEnterTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate enter: " + mRotateEnterTransformation);
+ }
+
+ if (!moreFinishEnter && !moreRotateEnter) {
+ if (DEBUG_STATE) Slog.v(TAG, "Rotate enter animation done!");
+ mRotateEnterAnimation.cancel();
+ mRotateEnterAnimation = null;
+ mRotateEnterTransformation.clear();
+ }
+
+ mExitTransformation.set(mRotateExitTransformation);
+ mExitTransformation.compose(mStartExitTransformation);
+ mExitTransformation.compose(mFinishExitTransformation);
+
+ mEnterTransformation.set(mRotateEnterTransformation);
+ mEnterTransformation.compose(mStartEnterTransformation);
+ mEnterTransformation.compose(mFinishEnterTransformation);
+
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final exit: " + mExitTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final enter: " + mEnterTransformation);
+
+ if (!moreStartExit && !moreFinishExit && !moreRotateExit) {
+ if (mSurface != null) {
+ if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface");
+ mSurface.hide();
+ }
+ }
+
+ if (!moreStartEnter && !moreFinishEnter && !moreRotateEnter) {
+ if (mBlackFrame != null) {
+ if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, hiding black frame");
+ mBlackFrame.hide();
+ }
+ } else {
+ if (mBlackFrame != null) {
+ mBlackFrame.setMatrix(mEnterTransformation.getMatrix());
}
}
mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix);
setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
- return moreEnter || moreExit;
+ final boolean more = moreStartEnter || moreStartExit || moreFinishEnter || moreFinishExit
+ || moreRotateEnter || moreRotateExit || !mFinishAnimReady;
+
+ if (DEBUG_STATE) Slog.v(TAG, "Step: more=" + more);
+
+ return more;
}
public Transformation getEnterTransformation() {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 8ec97e1..19d94a1 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -2543,8 +2543,12 @@
if (win == null) {
return 0;
}
- win.mRequestedWidth = requestedWidth;
- win.mRequestedHeight = requestedHeight;
+ if (win.mRequestedWidth != requestedWidth
+ || win.mRequestedHeight != requestedHeight) {
+ win.mLayoutNeeded = true;
+ win.mRequestedWidth = requestedWidth;
+ win.mRequestedHeight = requestedHeight;
+ }
if (attrs != null && seq == win.mSeq) {
win.mSystemUiVisibility = systemUiVisibility;
}
@@ -2565,6 +2569,9 @@
}
flagChanges = win.mAttrs.flags ^= attrs.flags;
attrChanges = win.mAttrs.copyFrom(attrs);
+ if ((attrChanges&WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) {
+ win.mLayoutNeeded = true;
+ }
}
if (DEBUG_LAYOUT) Slog.v(TAG, "Relayout " + win + ": " + win.mAttrs);
@@ -3446,7 +3453,7 @@
// the value of the previous configuration.
mTempConfiguration.setToDefaults();
mTempConfiguration.fontScale = currentConfig.fontScale;
- if (computeNewConfigurationLocked(mTempConfiguration)) {
+ if (computeScreenConfigurationLocked(mTempConfiguration)) {
if (currentConfig.diff(mTempConfiguration) != 0) {
mWaitingForConfig = true;
mLayoutNeeded = true;
@@ -5362,6 +5369,14 @@
startFreezingDisplayLocked(inTransaction);
mInputManager.setDisplayOrientation(0, rotation);
+ // We need to update our screen size information to match the new
+ // rotation. Note that this is redundant with the later call to
+ // sendNewConfiguration() that must be called after this function
+ // returns... however we need to do the screen size part of that
+ // before then so we have the correct size to use when initializiation
+ // the rotation animation for the new rotation.
+ computeScreenConfigurationLocked(null);
+
if (!inTransaction) {
if (SHOW_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION setRotationUnchecked");
@@ -5372,7 +5387,11 @@
// it doesn't support hardware OpenGL emulation yet.
if (CUSTOM_SCREEN_ROTATION && mScreenRotationAnimation != null
&& mScreenRotationAnimation.hasScreenshot()) {
- mScreenRotationAnimation.setRotation(rotation);
+ if (mScreenRotationAnimation.setRotation(rotation, mFxSession,
+ MAX_ANIMATION_DURATION, mTransitionAnimationScale,
+ mCurDisplayWidth, mCurDisplayHeight)) {
+ requestAnimationLocked(0);
+ }
}
Surface.setOrientation(0, rotation);
} finally {
@@ -5860,7 +5879,7 @@
Configuration computeNewConfigurationLocked() {
Configuration config = new Configuration();
config.fontScale = 0;
- if (!computeNewConfigurationLocked(config)) {
+ if (!computeScreenConfigurationLocked(config)) {
return null;
}
return config;
@@ -6011,12 +6030,10 @@
return sw;
}
- boolean computeNewConfigurationLocked(Configuration config) {
+ boolean computeScreenConfigurationLocked(Configuration config) {
if (mDisplay == null) {
return false;
}
-
- mInputManager.getInputConfiguration(config);
// Use the effective "visual" dimensions based on current rotation
final boolean rotated = (mRotation == Surface.ROTATION_90
@@ -6050,13 +6067,17 @@
final int dw = mCurDisplayWidth;
final int dh = mCurDisplayHeight;
- int orientation = Configuration.ORIENTATION_SQUARE;
- if (dw < dh) {
- orientation = Configuration.ORIENTATION_PORTRAIT;
- } else if (dw > dh) {
- orientation = Configuration.ORIENTATION_LANDSCAPE;
+ if (config != null) {
+ mInputManager.getInputConfiguration(config);
+
+ int orientation = Configuration.ORIENTATION_SQUARE;
+ if (dw < dh) {
+ orientation = Configuration.ORIENTATION_PORTRAIT;
+ } else if (dw > dh) {
+ orientation = Configuration.ORIENTATION_LANDSCAPE;
+ }
+ config.orientation = orientation;
}
- config.orientation = orientation;
// Update real display metrics.
mDisplay.getMetricsWithSize(mRealDisplayMetrics, mCurDisplayWidth, mCurDisplayHeight);
@@ -6078,36 +6099,39 @@
mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm,
mCompatDisplayMetrics);
- config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation)
- / dm.density);
- config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation)
- / dm.density);
- computeSmallestWidthAndScreenLayout(rotated, dw, dh, dm.density, config);
+ if (config != null) {
+ config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation)
+ / dm.density);
+ config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation)
+ / dm.density);
+ computeSmallestWidthAndScreenLayout(rotated, dw, dh, dm.density, config);
- config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
- config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
- config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh);
+ config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
+ config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
+ config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh);
- // Determine whether a hard keyboard is available and enabled.
- boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
- if (hardKeyboardAvailable != mHardKeyboardAvailable) {
- mHardKeyboardAvailable = hardKeyboardAvailable;
- mHardKeyboardEnabled = hardKeyboardAvailable;
+ // Determine whether a hard keyboard is available and enabled.
+ boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
+ if (hardKeyboardAvailable != mHardKeyboardAvailable) {
+ mHardKeyboardAvailable = hardKeyboardAvailable;
+ mHardKeyboardEnabled = hardKeyboardAvailable;
- mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
- mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
- }
- if (!mHardKeyboardEnabled) {
- config.keyboard = Configuration.KEYBOARD_NOKEYS;
+ mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+ mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+ }
+ if (!mHardKeyboardEnabled) {
+ config.keyboard = Configuration.KEYBOARD_NOKEYS;
+ }
+
+ // Update value of keyboardHidden, hardKeyboardHidden and navigationHidden
+ // based on whether a hard or soft keyboard is present, whether navigation keys
+ // are present and the lid switch state.
+ config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
+ config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
+ config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
+ mPolicy.adjustConfigurationLw(config);
}
- // Update value of keyboardHidden, hardKeyboardHidden and navigationHidden
- // based on whether a hard or soft keyboard is present, whether navigation keys
- // are present and the lid switch state.
- config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
- config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
- config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
- mPolicy.adjustConfigurationLw(config);
return true;
}
@@ -7114,7 +7138,7 @@
boolean configChanged = updateOrientationFromAppTokensLocked(false);
mTempConfiguration.setToDefaults();
mTempConfiguration.fontScale = mCurConfiguration.fontScale;
- if (computeNewConfigurationLocked(mTempConfiguration)) {
+ if (computeScreenConfigurationLocked(mTempConfiguration)) {
if (mCurConfiguration.diff(mTempConfiguration) != 0) {
configChanged = true;
}
@@ -7443,12 +7467,13 @@
// if they want. (We do the normal layout for INVISIBLE
// windows, since that means "perform layout as normal,
// just don't display").
- if (!gone || !win.mHaveFrame) {
+ if (!gone || !win.mHaveFrame || win.mLayoutNeeded) {
if (!win.mLayoutAttached) {
if (initial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
win.mContentChanged = false;
}
+ win.mLayoutNeeded = false;
win.prelayout();
mPolicy.layoutWindowLw(win, win.mAttrs, null);
win.mLayoutSeq = seq;
@@ -7480,11 +7505,12 @@
// windows, since that means "perform layout as normal,
// just don't display").
if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
- || !win.mHaveFrame) {
+ || !win.mHaveFrame || win.mLayoutNeeded) {
if (initial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
win.mContentChanged = false;
}
+ win.mLayoutNeeded = false;
win.prelayout();
mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);
win.mLayoutSeq = seq;
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 794515b..1067cad 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -238,6 +238,12 @@
// we can give the window focus before waiting for the relayout.
boolean mRelayoutCalled;
+ // If the application has called relayout() with changes that can
+ // impact its window's size, we need to perform a layout pass on it
+ // even if it is not currently visible for layout. This is set
+ // when in that case until the layout is done.
+ boolean mLayoutNeeded;
+
// This is set after the Surface has been created but before the
// window has been drawn. During this time the surface is hidden.
boolean mDrawPending;
@@ -1728,8 +1734,9 @@
pw.print(mPolicyVisibilityAfterAnim);
pw.print(" mAttachedHidden="); pw.println(mAttachedHidden);
}
- if (!mRelayoutCalled) {
- pw.print(prefix); pw.print("mRelayoutCalled="); pw.println(mRelayoutCalled);
+ if (!mRelayoutCalled || mLayoutNeeded) {
+ pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled);
+ pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded);
}
if (mSurfaceResized || mSurfaceDestroyDeferred) {
pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized);
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 438a6da..986aec5 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -350,15 +350,28 @@
}
// this needs to be thread safe
-nsecs_t DisplayHardware::waitForVSync() const {
+nsecs_t DisplayHardware::waitForRefresh() const {
nsecs_t timestamp;
if (mVSync.wait(×tamp) < 0) {
// vsync not supported!
usleep( getDelayToNextVSyncUs(×tamp) );
}
+ mLastHwVSync = timestamp; // FIXME: Not thread safe
return timestamp;
}
+nsecs_t DisplayHardware::getRefreshTimestamp() const {
+ // this returns the last refresh timestamp.
+ // if the last one is not available, we estimate it based on
+ // the refresh period and whatever closest timestamp we have.
+ nsecs_t now = systemTime();
+ return now - ((now - mLastHwVSync) % mRefreshPeriod);
+}
+
+nsecs_t DisplayHardware::getRefreshPeriod() const {
+ return mRefreshPeriod;
+}
+
int32_t DisplayHardware::getDelayToNextVSyncUs(nsecs_t* timestamp) const {
Mutex::Autolock _l(mFakeVSyncMutex);
const nsecs_t period = mRefreshPeriod;
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
index 77da272..02be4dc 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
@@ -76,7 +76,9 @@
uint32_t getMaxViewportDims() const;
// waits for the next vsync and returns the timestamp of when it happened
- nsecs_t waitForVSync() const;
+ nsecs_t waitForRefresh() const;
+ nsecs_t getRefreshPeriod() const;
+ nsecs_t getRefreshTimestamp() const;
uint32_t getPageFlipCount() const;
EGLDisplay getEGLDisplay() const { return mDisplay; }
@@ -119,6 +121,7 @@
mutable Mutex mFakeVSyncMutex;
mutable nsecs_t mNextFakeVSync;
nsecs_t mRefreshPeriod;
+ mutable nsecs_t mLastHwVSync;
HWComposer* mHwc;
diff --git a/services/surfaceflinger/EventThread.cpp b/services/surfaceflinger/EventThread.cpp
index 80ab519..6796d7d 100644
--- a/services/surfaceflinger/EventThread.cpp
+++ b/services/surfaceflinger/EventThread.cpp
@@ -129,7 +129,7 @@
// at least one listener requested VSYNC
mLock.unlock();
- timestamp = mHw.waitForVSync();
+ timestamp = mHw.waitForRefresh();
mLock.lock();
mDeliveredEvents++;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index d4c4b1f..a294281 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -38,6 +38,7 @@
#include "Layer.h"
#include "SurfaceFlinger.h"
#include "SurfaceTextureLayer.h"
+#include <math.h>
#define DEBUG_RESIZE 0
@@ -54,6 +55,8 @@
mCurrentTransform(0),
mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
mCurrentOpacity(true),
+ mFrameLatencyNeeded(false),
+ mFrameLatencyOffset(0),
mFormat(PIXEL_FORMAT_NONE),
mGLExtensions(GLExtensions::getInstance()),
mOpaqueLayer(true),
@@ -65,6 +68,17 @@
glGenTextures(1, &mTextureName);
}
+void Layer::onLayerDisplayed() {
+ if (mFrameLatencyNeeded) {
+ const DisplayHardware& hw(graphicPlane(0).displayHardware());
+ mFrameStats[mFrameLatencyOffset].timestamp = mSurfaceTexture->getTimestamp();
+ mFrameStats[mFrameLatencyOffset].set = systemTime();
+ mFrameStats[mFrameLatencyOffset].vsync = hw.getRefreshTimestamp();
+ mFrameLatencyOffset = (mFrameLatencyOffset + 1) % 128;
+ mFrameLatencyNeeded = false;
+ }
+}
+
void Layer::onFirstRef()
{
LayerBaseClient::onFirstRef();
@@ -408,6 +422,7 @@
// update the active buffer
mActiveBuffer = mSurfaceTexture->getCurrentBuffer();
+ mFrameLatencyNeeded = true;
const Rect crop(mSurfaceTexture->getCurrentCrop());
const uint32_t transform(mSurfaceTexture->getCurrentTransform());
@@ -538,11 +553,33 @@
result.append(buffer);
+ LayerBase::dumpStats(result, buffer, SIZE);
+
if (mSurfaceTexture != 0) {
mSurfaceTexture->dump(result, " ", buffer, SIZE);
}
}
+void Layer::dumpStats(String8& result, char* buffer, size_t SIZE) const
+{
+ LayerBaseClient::dumpStats(result, buffer, SIZE);
+ const size_t o = mFrameLatencyOffset;
+ const DisplayHardware& hw(graphicPlane(0).displayHardware());
+ const nsecs_t period = hw.getRefreshPeriod();
+ result.appendFormat("%lld\n", period);
+ for (size_t i=0 ; i<128 ; i++) {
+ const size_t index = (o+i) % 128;
+ const nsecs_t time_app = mFrameStats[index].timestamp;
+ const nsecs_t time_set = mFrameStats[index].set;
+ const nsecs_t time_vsync = mFrameStats[index].vsync;
+ result.appendFormat("%lld\t%lld\t%lld\n",
+ time_app,
+ time_vsync,
+ time_set);
+ }
+ result.append("\n");
+}
+
uint32_t Layer::getEffectiveUsage(uint32_t usage) const
{
// TODO: should we do something special if mSecure is set?
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 2b9471b..b3fa5e7 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -34,6 +34,7 @@
#include "LayerBase.h"
#include "SurfaceTextureLayer.h"
#include "Transform.h"
+#include <utils/Timers.h>
namespace android {
@@ -78,12 +79,15 @@
// LayerBaseClient interface
virtual wp<IBinder> getSurfaceTextureBinder() const;
+ virtual void onLayerDisplayed();
+
// only for debugging
inline const sp<GraphicBuffer>& getActiveBuffer() const { return mActiveBuffer; }
protected:
virtual void onFirstRef();
virtual void dump(String8& result, char* scratch, size_t size) const;
+ virtual void dumpStats(String8& result, char* buffer, size_t SIZE) const;
private:
friend class SurfaceTextureLayer;
@@ -110,6 +114,16 @@
uint32_t mCurrentTransform;
uint32_t mCurrentScalingMode;
bool mCurrentOpacity;
+ bool mFrameLatencyNeeded;
+ int mFrameLatencyOffset;
+ struct Statistics {
+ Statistics() : timestamp(0), set(0), vsync(0) { }
+ nsecs_t timestamp; // buffer timestamp
+ nsecs_t set; // buffer displayed timestamp
+ nsecs_t vsync; // vsync immediately before set
+ };
+ // protected by mLock
+ Statistics mFrameStats[128];
// constants
PixelFormat mFormat;
@@ -121,9 +135,6 @@
bool mSecure; // no screenshots
bool mProtectedByApp; // application requires protected path to external sink
Region mPostedDirtyRegion;
-
- // binder thread, transaction thread
- mutable Mutex mLock;
};
// ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp
index 37879f1..1e2c4cb 100644
--- a/services/surfaceflinger/LayerBase.cpp
+++ b/services/surfaceflinger/LayerBase.cpp
@@ -471,6 +471,9 @@
void LayerBase::dump(String8& result, char* buffer, size_t SIZE) const
{
const Layer::State& s(drawingState());
+ s.transparentRegion.dump(result, "transparentRegion");
+ transparentRegionScreen.dump(result, "transparentRegionScreen");
+ visibleRegionScreen.dump(result, "visibleRegionScreen");
snprintf(buffer, SIZE,
"+ %s %p (%s)\n"
" "
@@ -491,6 +494,9 @@
LayerBase::dump(result, scratch, size);
}
+void LayerBase::dumpStats(String8& result, char* scratch, size_t SIZE) const
+{
+}
// ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h
index 7f62145..03d2cc6 100644
--- a/services/surfaceflinger/LayerBase.h
+++ b/services/surfaceflinger/LayerBase.h
@@ -205,10 +205,13 @@
/** called with the state lock when the surface is removed from the
* current list */
virtual void onRemoved() { };
-
+
+ virtual void onLayerDisplayed() { };
+
/** always call base class first */
virtual void dump(String8& result, char* scratch, size_t size) const;
virtual void shortDump(String8& result, char* scratch, size_t size) const;
+ virtual void dumpStats(String8& result, char* buffer, size_t SIZE) const;
enum { // flags for doTransaction()
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index af47402..883b642 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -431,7 +431,7 @@
} else {
// pretend we did the post
hw.compositionComplete();
- hw.waitForVSync();
+ hw.waitForRefresh();
}
return true;
}
@@ -445,6 +445,12 @@
const nsecs_t now = systemTime();
mDebugInSwapBuffers = now;
hw.flip(mSwapRegion);
+
+ size_t numLayers = mVisibleLayersSortedByZ.size();
+ for (size_t i = 0; i < numLayers; i++) {
+ mVisibleLayersSortedByZ[i]->onLayerDisplayed();
+ }
+
mLastSwapBufferTime = systemTime() - now;
mDebugInSwapBuffers = 0;
mSwapRegion.clear();
@@ -1463,14 +1469,6 @@
IPCThreadState::self()->getCallingUid());
result.append(buffer);
} else {
-
- // figure out if we're stuck somewhere
- const nsecs_t now = systemTime();
- const nsecs_t inSwapBuffers(mDebugInSwapBuffers);
- const nsecs_t inTransaction(mDebugInTransaction);
- nsecs_t inSwapBuffersDuration = (inSwapBuffers) ? now-inSwapBuffers : 0;
- nsecs_t inTransactionDuration = (inTransaction) ? now-inTransaction : 0;
-
// Try to get the main lock, but don't insist if we can't
// (this would indicate SF is stuck, but we want to be able to
// print something in dumpsys).
@@ -1486,111 +1484,20 @@
result.append(buffer);
}
- /*
- * Dump the visible layer list
- */
- const LayerVector& currentLayers = mCurrentState.layersSortedByZ;
- const size_t count = currentLayers.size();
- snprintf(buffer, SIZE, "Visible layers (count = %d)\n", count);
- result.append(buffer);
- for (size_t i=0 ; i<count ; i++) {
- const sp<LayerBase>& layer(currentLayers[i]);
- layer->dump(result, buffer, SIZE);
- const Layer::State& s(layer->drawingState());
- s.transparentRegion.dump(result, "transparentRegion");
- layer->transparentRegionScreen.dump(result, "transparentRegionScreen");
- layer->visibleRegionScreen.dump(result, "visibleRegionScreen");
+ bool dumpAll = true;
+ size_t index = 0;
+ if (args.size()) {
+ dumpAll = false;
+ if (args[index] == String16("--latency")) {
+ index++;
+ dumpStatsLocked(args, index, result, buffer, SIZE);
+ }
}
- /*
- * Dump the layers in the purgatory
- */
-
- const size_t purgatorySize = mLayerPurgatory.size();
- snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize);
- result.append(buffer);
- for (size_t i=0 ; i<purgatorySize ; i++) {
- const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i));
- layer->shortDump(result, buffer, SIZE);
+ if (dumpAll) {
+ dumpAllLocked(result, buffer, SIZE);
}
- /*
- * Dump SurfaceFlinger global state
- */
-
- snprintf(buffer, SIZE, "SurfaceFlinger global state:\n");
- result.append(buffer);
-
- const GLExtensions& extensions(GLExtensions::getInstance());
- snprintf(buffer, SIZE, "GLES: %s, %s, %s\n",
- extensions.getVendor(),
- extensions.getRenderer(),
- extensions.getVersion());
- result.append(buffer);
-
- snprintf(buffer, SIZE, "EGL : %s\n",
- eglQueryString(graphicPlane(0).getEGLDisplay(),
- EGL_VERSION_HW_ANDROID));
- result.append(buffer);
-
- snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension());
- result.append(buffer);
-
- mWormholeRegion.dump(result, "WormholeRegion");
- const DisplayHardware& hw(graphicPlane(0).displayHardware());
- snprintf(buffer, SIZE,
- " orientation=%d, canDraw=%d\n",
- mCurrentState.orientation, hw.canDraw());
- result.append(buffer);
- snprintf(buffer, SIZE,
- " last eglSwapBuffers() time: %f us\n"
- " last transaction time : %f us\n"
- " refresh-rate : %f fps\n"
- " x-dpi : %f\n"
- " y-dpi : %f\n",
- mLastSwapBufferTime/1000.0,
- mLastTransactionTime/1000.0,
- hw.getRefreshRate(),
- hw.getDpiX(),
- hw.getDpiY());
- result.append(buffer);
-
- if (inSwapBuffersDuration || !locked) {
- snprintf(buffer, SIZE, " eglSwapBuffers time: %f us\n",
- inSwapBuffersDuration/1000.0);
- result.append(buffer);
- }
-
- if (inTransactionDuration || !locked) {
- snprintf(buffer, SIZE, " transaction time: %f us\n",
- inTransactionDuration/1000.0);
- result.append(buffer);
- }
-
- /*
- * VSYNC state
- */
- mEventThread->dump(result, buffer, SIZE);
-
- /*
- * Dump HWComposer state
- */
- HWComposer& hwc(hw.getHwComposer());
- snprintf(buffer, SIZE, "h/w composer state:\n");
- result.append(buffer);
- snprintf(buffer, SIZE, " h/w composer %s and %s\n",
- hwc.initCheck()==NO_ERROR ? "present" : "not present",
- (mDebugDisableHWC || mDebugRegion) ? "disabled" : "enabled");
- result.append(buffer);
- hwc.dump(result, buffer, SIZE, mVisibleLayersSortedByZ);
-
- /*
- * Dump gralloc state
- */
- const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
- alloc.dump(result);
- hw.dump(result);
-
if (locked) {
mStateLock.unlock();
}
@@ -1599,6 +1506,137 @@
return NO_ERROR;
}
+void SurfaceFlinger::dumpStatsLocked(const Vector<String16>& args, size_t& index,
+ String8& result, char* buffer, size_t SIZE) const
+{
+ String8 name;
+ if (index < args.size()) {
+ name = String8(args[index]);
+ index++;
+ }
+
+ const LayerVector& currentLayers = mCurrentState.layersSortedByZ;
+ const size_t count = currentLayers.size();
+ for (size_t i=0 ; i<count ; i++) {
+ const sp<LayerBase>& layer(currentLayers[i]);
+ if (name.isEmpty()) {
+ snprintf(buffer, SIZE, "%s\n", layer->getName().string());
+ result.append(buffer);
+ }
+ if (name.isEmpty() || (name == layer->getName())) {
+ layer->dumpStats(result, buffer, SIZE);
+ }
+ }
+}
+
+void SurfaceFlinger::dumpAllLocked(
+ String8& result, char* buffer, size_t SIZE) const
+{
+ // figure out if we're stuck somewhere
+ const nsecs_t now = systemTime();
+ const nsecs_t inSwapBuffers(mDebugInSwapBuffers);
+ const nsecs_t inTransaction(mDebugInTransaction);
+ nsecs_t inSwapBuffersDuration = (inSwapBuffers) ? now-inSwapBuffers : 0;
+ nsecs_t inTransactionDuration = (inTransaction) ? now-inTransaction : 0;
+
+ /*
+ * Dump the visible layer list
+ */
+ const LayerVector& currentLayers = mCurrentState.layersSortedByZ;
+ const size_t count = currentLayers.size();
+ snprintf(buffer, SIZE, "Visible layers (count = %d)\n", count);
+ result.append(buffer);
+ for (size_t i=0 ; i<count ; i++) {
+ const sp<LayerBase>& layer(currentLayers[i]);
+ layer->dump(result, buffer, SIZE);
+ }
+
+ /*
+ * Dump the layers in the purgatory
+ */
+
+ const size_t purgatorySize = mLayerPurgatory.size();
+ snprintf(buffer, SIZE, "Purgatory state (%d entries)\n", purgatorySize);
+ result.append(buffer);
+ for (size_t i=0 ; i<purgatorySize ; i++) {
+ const sp<LayerBase>& layer(mLayerPurgatory.itemAt(i));
+ layer->shortDump(result, buffer, SIZE);
+ }
+
+ /*
+ * Dump SurfaceFlinger global state
+ */
+
+ snprintf(buffer, SIZE, "SurfaceFlinger global state:\n");
+ result.append(buffer);
+
+ const GLExtensions& extensions(GLExtensions::getInstance());
+ snprintf(buffer, SIZE, "GLES: %s, %s, %s\n",
+ extensions.getVendor(),
+ extensions.getRenderer(),
+ extensions.getVersion());
+ result.append(buffer);
+
+ snprintf(buffer, SIZE, "EGL : %s\n",
+ eglQueryString(graphicPlane(0).getEGLDisplay(),
+ EGL_VERSION_HW_ANDROID));
+ result.append(buffer);
+
+ snprintf(buffer, SIZE, "EXTS: %s\n", extensions.getExtension());
+ result.append(buffer);
+
+ mWormholeRegion.dump(result, "WormholeRegion");
+ const DisplayHardware& hw(graphicPlane(0).displayHardware());
+ snprintf(buffer, SIZE,
+ " orientation=%d, canDraw=%d\n",
+ mCurrentState.orientation, hw.canDraw());
+ result.append(buffer);
+ snprintf(buffer, SIZE,
+ " last eglSwapBuffers() time: %f us\n"
+ " last transaction time : %f us\n"
+ " refresh-rate : %f fps\n"
+ " x-dpi : %f\n"
+ " y-dpi : %f\n",
+ mLastSwapBufferTime/1000.0,
+ mLastTransactionTime/1000.0,
+ hw.getRefreshRate(),
+ hw.getDpiX(),
+ hw.getDpiY());
+ result.append(buffer);
+
+ snprintf(buffer, SIZE, " eglSwapBuffers time: %f us\n",
+ inSwapBuffersDuration/1000.0);
+ result.append(buffer);
+
+ snprintf(buffer, SIZE, " transaction time: %f us\n",
+ inTransactionDuration/1000.0);
+ result.append(buffer);
+
+ /*
+ * VSYNC state
+ */
+ mEventThread->dump(result, buffer, SIZE);
+
+ /*
+ * Dump HWComposer state
+ */
+ HWComposer& hwc(hw.getHwComposer());
+ snprintf(buffer, SIZE, "h/w composer state:\n");
+ result.append(buffer);
+ snprintf(buffer, SIZE, " h/w composer %s and %s\n",
+ hwc.initCheck()==NO_ERROR ? "present" : "not present",
+ (mDebugDisableHWC || mDebugRegion) ? "disabled" : "enabled");
+ result.append(buffer);
+ hwc.dump(result, buffer, SIZE, mVisibleLayersSortedByZ);
+
+ /*
+ * Dump gralloc state
+ */
+ const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
+ alloc.dump(result);
+ hw.dump(result);
+}
+
status_t SurfaceFlinger::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 7f6c90c..c976e5a 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -337,6 +337,9 @@
void debugFlashRegions();
void drawWormhole() const;
+ void dumpStatsLocked(const Vector<String16>& args, size_t& index,
+ String8& result, char* buffer, size_t SIZE) const;
+ void dumpAllLocked(String8& result, char* buffer, size_t SIZE) const;
mutable MessageQueue mEventQueue;
diff --git a/services/tests/servicestests/res/raw/netstats_uid_v4 b/services/tests/servicestests/res/raw/netstats_uid_v4
new file mode 100644
index 0000000..e75fc1c
--- /dev/null
+++ b/services/tests/servicestests/res/raw/netstats_uid_v4
Binary files differ
diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/services/tests/servicestests/res/raw/netstats_v1
new file mode 100644
index 0000000..e80860a
--- /dev/null
+++ b/services/tests/servicestests/res/raw/netstats_v1
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 90b5a2e..8f5e77e 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -39,6 +39,7 @@
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
import static org.easymock.EasyMock.anyLong;
+import static org.easymock.EasyMock.aryEq;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
@@ -63,10 +64,12 @@
import android.telephony.TelephonyManager;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.Suppress;
import android.util.TrustedTime;
import com.android.server.net.NetworkStatsService;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
+import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
import org.easymock.Capture;
import org.easymock.EasyMock;
@@ -89,6 +92,10 @@
private static final String IMSI_1 = "310004";
private static final String IMSI_2 = "310260";
+ private static final long KB_IN_BYTES = 1024;
+ private static final long MB_IN_BYTES = 1024 * KB_IN_BYTES;
+ private static final long GB_IN_BYTES = 1024 * MB_IN_BYTES;
+
private static NetworkTemplate sTemplateWifi = buildTemplateWifi();
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
@@ -282,13 +289,6 @@
mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SHUTDOWN));
verifyAndReset();
- // talk with zombie service to assert stats have gone; and assert that
- // we persisted them to file.
- expectDefaultSettings();
- replay();
- assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
- verifyAndReset();
-
assertStatsFilesExist(true);
// boot through serviceReady() again
@@ -319,6 +319,8 @@
}
+ // TODO: simulate reboot to test bucket resize
+ @Suppress
public void testStatsBucketResize() throws Exception {
NetworkStatsHistory history = null;
@@ -602,7 +604,6 @@
assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10);
verifyAndReset();
-
}
public void testSummaryForAllUid() throws Exception {
@@ -755,11 +756,15 @@
expectDefaultSettings();
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
- expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L));
+
+ final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
final String[] tetherIfacePairs = new String[] { TEST_IFACE, "wlan0" };
- expectNetworkStatsPoll(tetherIfacePairs, new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L));
+ final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
+ .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, 0L);
+
+ expectNetworkStatsUidDetail(uidStats, tetherIfacePairs, tetherStats);
+ expectNetworkStatsPoll();
replay();
mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
@@ -808,6 +813,9 @@
private void expectNetworkState(NetworkState... state) throws Exception {
expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce();
+
+ final LinkProperties linkProp = state.length > 0 ? state[0].linkProperties : null;
+ expect(mConnManager.getActiveLinkProperties()).andReturn(linkProp).atLeastOnce();
}
private void expectNetworkStatsSummary(NetworkStats summary) throws Exception {
@@ -815,23 +823,35 @@
}
private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception {
+ expectNetworkStatsUidDetail(detail, new String[0], new NetworkStats(0L, 0));
+ }
+
+ private void expectNetworkStatsUidDetail(
+ NetworkStats detail, String[] tetherIfacePairs, NetworkStats tetherStats)
+ throws Exception {
expect(mNetManager.getNetworkStatsUidDetail(eq(UID_ALL))).andReturn(detail).atLeastOnce();
+
+ // also include tethering details, since they are folded into UID
+ expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).atLeastOnce();
+ expect(mNetManager.getNetworkStatsTethering(aryEq(tetherIfacePairs)))
+ .andReturn(tetherStats).atLeastOnce();
}
private void expectDefaultSettings() throws Exception {
expectSettings(0L, HOUR_IN_MILLIS, WEEK_IN_MILLIS);
}
- private void expectSettings(long persistThreshold, long bucketDuration, long maxHistory)
+ private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
throws Exception {
expect(mSettings.getPollInterval()).andReturn(HOUR_IN_MILLIS).anyTimes();
- expect(mSettings.getPersistThreshold()).andReturn(persistThreshold).anyTimes();
- expect(mSettings.getNetworkBucketDuration()).andReturn(bucketDuration).anyTimes();
- expect(mSettings.getNetworkMaxHistory()).andReturn(maxHistory).anyTimes();
- expect(mSettings.getUidBucketDuration()).andReturn(bucketDuration).anyTimes();
- expect(mSettings.getUidMaxHistory()).andReturn(maxHistory).anyTimes();
- expect(mSettings.getTagMaxHistory()).andReturn(maxHistory).anyTimes();
expect(mSettings.getTimeCacheMaxAge()).andReturn(DAY_IN_MILLIS).anyTimes();
+ expect(mSettings.getGlobalAlertBytes()).andReturn(MB_IN_BYTES).anyTimes();
+ expect(mSettings.getSampleEnabled()).andReturn(true).anyTimes();
+
+ final Config config = new Config(bucketDuration, persistBytes, deleteAge, deleteAge);
+ expect(mSettings.getDevConfig()).andReturn(config).anyTimes();
+ expect(mSettings.getUidConfig()).andReturn(config).anyTimes();
+ expect(mSettings.getUidTagConfig()).andReturn(config).anyTimes();
}
private void expectCurrentTime() throws Exception {
@@ -843,27 +863,16 @@
}
private void expectNetworkStatsPoll() throws Exception {
- expectNetworkStatsPoll(new String[0], new NetworkStats(getElapsedRealtime(), 0));
- }
-
- private void expectNetworkStatsPoll(String[] tetherIfacePairs, NetworkStats tetherStats)
- throws Exception {
mNetManager.setGlobalAlert(anyLong());
expectLastCall().anyTimes();
- expect(mConnManager.getTetheredIfacePairs()).andReturn(tetherIfacePairs).anyTimes();
- expect(mNetManager.getNetworkStatsTethering(eq(tetherIfacePairs)))
- .andReturn(tetherStats).anyTimes();
}
private void assertStatsFilesExist(boolean exist) {
- final File networkFile = new File(mStatsDir, "netstats.bin");
- final File uidFile = new File(mStatsDir, "netstats_uid.bin");
+ final File basePath = new File(mStatsDir, "netstats");
if (exist) {
- assertTrue(networkFile.exists());
- assertTrue(uidFile.exists());
+ assertTrue(basePath.list().length > 0);
} else {
- assertFalse(networkFile.exists());
- assertFalse(uidFile.exists());
+ assertTrue(basePath.list().length == 0);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
new file mode 100644
index 0000000..7f05f56
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkStatsCollectionTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.server.net;
+
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.frameworks.servicestests.R;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * Tests for {@link NetworkStatsCollection}.
+ */
+@MediumTest
+public class NetworkStatsCollectionTest extends AndroidTestCase {
+
+ private static final String TEST_FILE = "test.bin";
+ private static final String TEST_IMSI = "310260000000000";
+
+ public void testReadLegacyNetwork() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_v1, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyNetwork(testFile);
+
+ // verify that history read correctly
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 636014522L, 709291L, 88037144L, 518820L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 636014522L, 709291L, 88037144L, 518820L);
+ }
+
+ public void testReadLegacyUid() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_uid_v4, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyUid(testFile, false);
+
+ // verify that history read correctly
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 637073904L, 711398L, 88342093L, 521006L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
+ 637073904L, 711398L, 88342093L, 521006L);
+ }
+
+ public void testReadLegacyUidTags() throws Exception {
+ final File testFile = new File(getContext().getFilesDir(), TEST_FILE);
+ stageFile(R.raw.netstats_uid_v4, testFile);
+
+ final NetworkStatsCollection collection = new NetworkStatsCollection(30 * MINUTE_IN_MILLIS);
+ collection.readLegacyUid(testFile, true);
+
+ // verify that history read correctly
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 77017831L, 100995L, 35436758L, 92344L);
+
+ // now export into a unified format
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ collection.write(new DataOutputStream(bos));
+
+ // clear structure completely
+ collection.reset();
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 0L, 0L, 0L, 0L);
+
+ // and read back into structure, verifying that totals are same
+ collection.read(new ByteArrayInputStream(bos.toByteArray()));
+ assertSummaryTotalIncludingTags(collection, buildTemplateMobileAll(TEST_IMSI),
+ 77017831L, 100995L, 35436758L, 92344L);
+ }
+
+ /**
+ * Copy a {@link Resources#openRawResource(int)} into {@link File} for
+ * testing purposes.
+ */
+ private void stageFile(int rawId, File file) throws Exception {
+ new File(file.getParent()).mkdirs();
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = getContext().getResources().openRawResource(rawId);
+ out = new FileOutputStream(file);
+ Streams.copy(in, out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ public static NetworkIdentitySet buildWifiIdent() {
+ final NetworkIdentitySet set = new NetworkIdentitySet();
+ set.add(new NetworkIdentity(ConnectivityManager.TYPE_WIFI, 0, null, false));
+ return set;
+ }
+
+ private static void assertSummaryTotal(NetworkStatsCollection collection,
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final NetworkStats.Entry entry = collection.getSummary(
+ template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+ assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
+ NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ final NetworkStats.Entry entry = collection.getSummary(
+ template, Long.MIN_VALUE, Long.MAX_VALUE).getTotalIncludingTags(null);
+ assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
+ }
+
+ private static void assertEntry(
+ NetworkStats.Entry entry, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+ assertEquals("unexpected rxBytes", rxBytes, entry.rxBytes);
+ assertEquals("unexpected rxPackets", rxPackets, entry.rxPackets);
+ assertEquals("unexpected txBytes", txBytes, entry.txBytes);
+ assertEquals("unexpected txPackets", txPackets, entry.txPackets);
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 1336818..d0e304f 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -328,8 +328,11 @@
String reason = null;
if (dp.onCompletedMsg != null) {
+ // Get ApnContext, but only valid on GSM devices this is a string on CDMA devices.
Message msg = dp.onCompletedMsg;
- alreadySent = (ApnContext)msg.obj;
+ if (msg.obj instanceof ApnContext) {
+ alreadySent = (ApnContext)msg.obj;
+ }
reason = dp.reason;
if (VDBG) {
log(String.format("msg=%s msg.obj=%s", msg.toString(),
diff --git a/telephony/java/com/android/internal/telephony/cat/CatService.java b/telephony/java/com/android/internal/telephony/cat/CatService.java
index 97fb73d..2b37072 100644
--- a/telephony/java/com/android/internal/telephony/cat/CatService.java
+++ b/telephony/java/com/android/internal/telephony/cat/CatService.java
@@ -427,11 +427,11 @@
}
break;
default:
- CatLog.d(this, "encodeOptionalTags() Unsupported Cmd:" + cmdDet.typeOfCommand);
+ CatLog.d(this, "encodeOptionalTags() Unsupported Cmd details=" + cmdDet);
break;
}
} else {
- CatLog.d(this, "encodeOptionalTags() bad Cmd:" + cmdDet.typeOfCommand);
+ CatLog.d(this, "encodeOptionalTags() bad Cmd details=" + cmdDet);
}
}
diff --git a/telephony/java/com/android/internal/telephony/cat/ComprehensionTlv.java b/telephony/java/com/android/internal/telephony/cat/ComprehensionTlv.java
index bd6b7d1..22cd5a4 100644
--- a/telephony/java/com/android/internal/telephony/cat/ComprehensionTlv.java
+++ b/telephony/java/com/android/internal/telephony/cat/ComprehensionTlv.java
@@ -96,7 +96,6 @@
startIndex = ctlv.mValueIndex + ctlv.mLength;
} else {
CatLog.d(LOG_TAG, "decodeMany: ctlv is null, stop decoding");
- items.clear();
break;
}
}
diff --git a/tests/ActivityTests/res/anim/slow_enter.xml b/tests/ActivityTests/res/anim/slow_enter.xml
new file mode 100644
index 0000000..0309643
--- /dev/null
+++ b/tests/ActivityTests/res/anim/slow_enter.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" >
+ <scale android:fromXScale="0.9" android:toXScale="1.5"
+ android:fromYScale="0.9" android:toYScale="1.5"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/slow_enter"
+ android:duration="40000" />
+ <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:interpolator="@android:interpolator/decelerate_cubic"
+ android:duration="1000" />
+</set>
diff --git a/tests/ActivityTests/res/anim/slow_exit.xml b/tests/ActivityTests/res/anim/slow_exit.xml
new file mode 100644
index 0000000..6cd3114
--- /dev/null
+++ b/tests/ActivityTests/res/anim/slow_exit.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false" >
+ <scale android:fromXScale="1.0" android:toXScale="0.9"
+ android:fromYScale="1.0" android:toYScale="0.9"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@android:interpolator/decelerate_quint"
+ android:duration="300" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:interpolator="@android:interpolator/decelerate_cubic"
+ android:duration="300"/>
+</set>
diff --git a/tests/ActivityTests/res/interpolator/slow_enter.xml b/tests/ActivityTests/res/interpolator/slow_enter.xml
new file mode 100644
index 0000000..ddab1aa
--- /dev/null
+++ b/tests/ActivityTests/res/interpolator/slow_enter.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+ android:cycles="10" />
diff --git a/tests/ActivityTests/res/values/themes.xml b/tests/ActivityTests/res/values/themes.xml
new file mode 100644
index 0000000..67f5938
--- /dev/null
+++ b/tests/ActivityTests/res/values/themes.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <style name="SlowDialog" parent="@android:style/Theme.Holo.Dialog">
+ <item name="android:windowAnimationStyle">@style/SlowDialog</item>
+ </style>
+ <style name="SlowDialog">
+ <item name="android:windowEnterAnimation">@anim/slow_enter</item>
+ <item name="android:windowExitAnimation">@anim/slow_exit</item>
+ </style>
+</resources>
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index 583c13c..ae42e29 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -22,6 +22,7 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityThread;
+import android.app.AlertDialog;
import android.app.Application;
import android.content.ActivityNotFoundException;
import android.os.Bundle;
@@ -35,6 +36,8 @@
import android.widget.TextView;
import android.widget.ScrollView;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -101,6 +104,20 @@
}
@Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add("Animate!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(ActivityTestMain.this,
+ R.style.SlowDialog);
+ builder.setTitle("This is a title");
+ builder.show();
+ return true;
+ }
+ });
+ return true;
+ }
+
+ @Override
protected void onStart() {
super.onStart();
buildUi();
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 112c606..5bbcce3 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -31,6 +31,15 @@
android:hardwareAccelerated="true">
<activity
+ android:name="PaintDrawFilterActivity"
+ android:label="_DrawFilter">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:name="DisplayListLayersActivity"
android:label="__DisplayListLayers">
<intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PaintDrawFilterActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PaintDrawFilterActivity.java
new file mode 100644
index 0000000..8523272
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PaintDrawFilterActivity.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class PaintDrawFilterActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(new CustomTextView(this));
+ }
+
+ static class CustomTextView extends View {
+ private final Paint mMediumPaint;
+ private final PaintFlagsDrawFilter mDrawFilter;
+
+ CustomTextView(Context c) {
+ super(c);
+
+ mMediumPaint = new Paint();
+ mMediumPaint.setAntiAlias(true);
+ mMediumPaint.setColor(0xff000000);
+ mMediumPaint.setFakeBoldText(true);
+ mMediumPaint.setTextSize(24.0f);
+
+ mDrawFilter = new PaintFlagsDrawFilter(
+ Paint.FAKE_BOLD_TEXT_FLAG, Paint.UNDERLINE_TEXT_FLAG);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawRGB(255, 255, 255);
+
+ canvas.setDrawFilter(null);
+ canvas.drawText("Hello OpenGL renderer!", 100, 120, mMediumPaint);
+ canvas.setDrawFilter(mDrawFilter);
+ canvas.drawText("Hello OpenGL renderer!", 100, 220, mMediumPaint);
+ }
+ }
+}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
index c038478..5ab2c58 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008-2011 The Android Open Source Project
+ * Copyright (C) 2008-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.
@@ -72,6 +72,7 @@
unitTests.add(new UT_alloc(this, mRes, mCtx));
unitTests.add(new UT_refcount(this, mRes, mCtx));
unitTests.add(new UT_foreach(this, mRes, mCtx));
+ unitTests.add(new UT_atomic(this, mRes, mCtx));
unitTests.add(new UT_math(this, mRes, mCtx));
unitTests.add(new UT_fp_mad(this, mRes, mCtx));
/*
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_atomic.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_atomic.java
new file mode 100644
index 0000000..267c5b2
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_atomic.java
@@ -0,0 +1,40 @@
+/*
+ * 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.rs.test;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.renderscript.*;
+
+public class UT_atomic extends UnitTest {
+ private Resources mRes;
+
+ protected UT_atomic(RSTestCore rstc, Resources res, Context ctx) {
+ super(rstc, "Atomics", ctx);
+ mRes = res;
+ }
+
+ public void run() {
+ RenderScript pRS = RenderScript.create(mCtx);
+ ScriptC_atomic s = new ScriptC_atomic(pRS, mRes, R.raw.atomic);
+ pRS.setMessageHandler(mRsMessage);
+ s.invoke_atomic_test();
+ pRS.finish();
+ waitForMessage();
+ pRS.destroy();
+ }
+}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/atomic.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/atomic.rs
new file mode 100644
index 0000000..f0a5041
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/atomic.rs
@@ -0,0 +1,77 @@
+#include "shared.rsh"
+
+// Testing atomic operations
+static bool testUMax(uint32_t dst, uint32_t src) {
+ bool failed = false;
+ uint32_t old = dst;
+ uint32_t expect = (dst > src ? dst : src);
+ uint32_t ret = rsAtomicMax(&dst, src);
+ _RS_ASSERT(old == ret);
+ _RS_ASSERT(dst == expect);
+ return failed;
+}
+
+static bool testUMin(uint32_t dst, uint32_t src) {
+ bool failed = false;
+ uint32_t old = dst;
+ uint32_t expect = (dst < src ? dst : src);
+ uint32_t ret = rsAtomicMin(&dst, src);
+ _RS_ASSERT(old == ret);
+ _RS_ASSERT(dst == expect);
+ return failed;
+}
+
+static bool testUCas(uint32_t dst, uint32_t cmp, uint32_t swp) {
+ bool failed = false;
+ uint32_t old = dst;
+ uint32_t expect = (dst == cmp ? swp : dst);
+ uint32_t ret = rsAtomicCas(&dst, cmp, swp);
+ _RS_ASSERT(old == ret);
+ _RS_ASSERT(dst == expect);
+ return failed;
+}
+
+static bool test_atomics() {
+ bool failed = false;
+
+ failed |= testUMax(5, 6);
+ failed |= testUMax(6, 5);
+ failed |= testUMax(5, 0xf0000006);
+ failed |= testUMax(0xf0000006, 5);
+
+ failed |= testUMin(5, 6);
+ failed |= testUMin(6, 5);
+ failed |= testUMin(5, 0xf0000006);
+ failed |= testUMin(0xf0000006, 5);
+
+ failed |= testUCas(4, 4, 5);
+ failed |= testUCas(4, 5, 5);
+ failed |= testUCas(5, 5, 4);
+ failed |= testUCas(5, 4, 4);
+ failed |= testUCas(0xf0000004, 0xf0000004, 0xf0000005);
+ failed |= testUCas(0xf0000004, 0xf0000005, 0xf0000005);
+ failed |= testUCas(0xf0000005, 0xf0000005, 0xf0000004);
+ failed |= testUCas(0xf0000005, 0xf0000004, 0xf0000004);
+
+ if (failed) {
+ rsDebug("test_atomics FAILED", 0);
+ }
+ else {
+ rsDebug("test_atomics PASSED", 0);
+ }
+
+ return failed;
+}
+
+void atomic_test() {
+ bool failed = false;
+ failed |= test_atomics();
+
+ if (failed) {
+ rsSendToClientBlocking(RS_MSG_TEST_FAILED);
+ }
+ else {
+ rsSendToClientBlocking(RS_MSG_TEST_PASSED);
+ }
+}
+
diff --git a/tests/TileBenchmark/res/values/strings.xml b/tests/TileBenchmark/res/values/strings.xml
index 5af52dc..6c7055b 100644
--- a/tests/TileBenchmark/res/values/strings.xml
+++ b/tests/TileBenchmark/res/values/strings.xml
@@ -49,8 +49,9 @@
<!-- Drop down menu entry - automatically scroll to the end of the page
with scrollBy() [CHAR LIMIT=15] -->
<string name="movement_auto_scroll">Auto-scroll</string>
- <!-- Drop down menu entry - [CHAR LIMIT=15] -->
- <string name="movement_auto_fling">Auto-fling</string>
+ <!-- Drop down menu entry - automatically record for a set time before
+ stopping [CHAR LIMIT=15] -->
+ <string name="movement_timed">Timed</string>
<!-- Drop down menu entry - manually navigate the page(s), hit 'capture'
button [CHAR LIMIT=15] -->
<string name="movement_manual">Manual</string>
@@ -67,14 +68,21 @@
<!-- 75th percentile - 75% of frames fall below this value [CHAR LIMIT=12]
-->
<string name="percentile_75">75%ile</string>
+ <!-- standard deviation [CHAR LIMIT=12] -->
+ <string name="std_dev">StdDev</string>
+ <!-- mean [CHAR LIMIT=12] -->
+ <string name="mean">mean</string>
+
+
+
<!-- Frame rate [CHAR LIMIT=15] -->
<string name="frames_per_second">Frames/sec</string>
<!-- Portion of viewport covered by good tiles [CHAR LIMIT=15] -->
<string name="viewport_coverage">Coverage</string>
<!-- Milliseconds taken to inval, and re-render the page [CHAR LIMIT=15] -->
<string name="render_millis">RenderMillis</string>
- <!-- Number of rendering stalls while running the test [CHAR LIMIT=15] -->
- <string name="render_stalls">Stalls</string>
+ <!-- Animation Framerate [CHAR LIMIT=15] -->
+ <string name="animation_framerate">AnimFramerate</string>
<!-- Format string for stat value overlay [CHAR LIMIT=15] -->
<string name="format_stat">%4.4f</string>
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java
index cc39b75..6356cc1 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PerformanceTest.java
@@ -27,17 +27,57 @@
import android.os.Environment;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
+import android.webkit.WebSettings;
+import android.widget.Spinner;
public class PerformanceTest extends
ActivityInstrumentationTestCase2<ProfileActivity> {
+ public static class AnimStat {
+ double aggVal = 0;
+ double aggSqrVal = 0;
+ double count = 0;
+ }
+
private class StatAggregator extends PlaybackGraphs {
private HashMap<String, Double> mDataMap = new HashMap<String, Double>();
+ private HashMap<String, AnimStat> mAnimDataMap = new HashMap<String, AnimStat>();
private int mCount = 0;
+
public void aggregate() {
+ boolean inAnimTests = mAnimTests != null;
+ Resources resources = mWeb.getResources();
+ String animFramerateString = resources.getString(R.string.animation_framerate);
+ for (Map.Entry<String, Double> e : mSingleStats.entrySet()) {
+ String name = e.getKey();
+ if (inAnimTests) {
+ if (name.equals(animFramerateString)) {
+ // in animation testing phase, record animation framerate and aggregate
+ // stats, differentiating on values of mAnimTestNr and mDoubleBuffering
+ String fullName = ANIM_TEST_NAMES[mAnimTestNr] + " " + name;
+ fullName += mDoubleBuffering ? " tiled" : " webkit";
+
+ if (!mAnimDataMap.containsKey(fullName)) {
+ mAnimDataMap.put(fullName, new AnimStat());
+ }
+ AnimStat statVals = mAnimDataMap.get(fullName);
+ statVals.aggVal += e.getValue();
+ statVals.aggSqrVal += e.getValue() * e.getValue();
+ statVals.count += 1;
+ }
+ } else {
+ double aggVal = mDataMap.containsKey(name)
+ ? mDataMap.get(name) : 0;
+ mDataMap.put(name, aggVal + e.getValue());
+ }
+ }
+
+ if (inAnimTests) {
+ return;
+ }
+
mCount++;
- Resources resources = mView.getResources();
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
String metricLabel = resources.getString(
@@ -53,34 +93,47 @@
mDataMap.put(label, aggVal);
}
}
- for (Map.Entry<String, Double> e : mSingleStats.entrySet()) {
- double aggVal = mDataMap.containsKey(e.getKey())
- ? mDataMap.get(e.getKey()) : 0;
- mDataMap.put(e.getKey(), aggVal + e.getValue());
- }
+
}
+ // build the final bundle of results
public Bundle getBundle() {
Bundle b = new Bundle();
- int count = 0 == mCount ? Integer.MAX_VALUE : mCount;
+ int count = (0 == mCount) ? Integer.MAX_VALUE : mCount;
for (Map.Entry<String, Double> e : mDataMap.entrySet()) {
b.putDouble(e.getKey(), e.getValue() / count);
}
+
+ for (Map.Entry<String, AnimStat> e : mAnimDataMap.entrySet()) {
+ String statName = e.getKey();
+ AnimStat statVals = e.getValue();
+
+ double avg = statVals.aggVal/statVals.count;
+ double stdDev = Math.sqrt((statVals.aggSqrVal / statVals.count) - avg * avg);
+
+ b.putDouble(statName, avg);
+ b.putDouble(statName + " STD DEV", stdDev);
+ }
+
return b;
}
}
ProfileActivity mActivity;
- ProfiledWebView mView;
- StatAggregator mStats = new StatAggregator();
+ ProfiledWebView mWeb;
+ Spinner mMovementSpinner;
+ StatAggregator mStats;
private static final String LOGTAG = "PerformanceTest";
private static final String TEST_LOCATION = "webkit/page_cycler";
private static final String URL_PREFIX = "file://";
private static final String URL_POSTFIX = "/index.html?skip=true";
private static final int MAX_ITERATIONS = 4;
- private static final String TEST_DIRS[] = {
- "alexa25_2011"//, "alexa_us", "android", "dom", "intl2", "moz", "moz2"
+ private static final String SCROLL_TEST_DIRS[] = {
+ "alexa25_2011"
+ };
+ private static final String ANIM_TEST_DIRS[] = {
+ "dhtml"
};
public PerformanceTest() {
@@ -91,7 +144,22 @@
protected void setUp() throws Exception {
super.setUp();
mActivity = getActivity();
- mView = (ProfiledWebView) mActivity.findViewById(R.id.web);
+ mWeb = (ProfiledWebView) mActivity.findViewById(R.id.web);
+ mMovementSpinner = (Spinner) mActivity.findViewById(R.id.movement);
+ mStats = new StatAggregator();
+
+ // use mStats as a condition variable between the UI thread and
+ // this(the testing) thread
+ mActivity.setCallback(new ProfileCallback() {
+ @Override
+ public void profileCallback(RunData data) {
+ mStats.setData(data);
+ synchronized (mStats) {
+ mStats.notify();
+ }
+ }
+ });
+
}
private boolean loadUrl(final String url) {
@@ -100,12 +168,13 @@
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
- mView.loadUrl(url);
+ mWeb.loadUrl(url);
}
});
synchronized (mStats) {
mStats.wait();
}
+
mStats.aggregate();
} catch (InterruptedException e) {
e.printStackTrace();
@@ -114,15 +183,30 @@
return true;
}
- private boolean runIteration() {
+ private boolean validTest(String nextTest) {
+ // if testing animations, test must be in mAnimTests
+ if (mAnimTests == null)
+ return true;
+
+ for (String test : mAnimTests) {
+ if (test.equals(nextTest)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean runIteration(String[] testDirs) {
File sdFile = Environment.getExternalStorageDirectory();
- for (String testDirName : TEST_DIRS) {
+ for (String testDirName : testDirs) {
File testDir = new File(sdFile, TEST_LOCATION + "/" + testDirName);
Log.d(LOGTAG, "Testing dir: '" + testDir.getAbsolutePath()
+ "', exists=" + testDir.exists());
+
for (File siteDir : testDir.listFiles()) {
- if (!siteDir.isDirectory())
+ if (!siteDir.isDirectory() || !validTest(siteDir.getName())) {
continue;
+ }
if (!loadUrl(URL_PREFIX + siteDir.getAbsolutePath()
+ URL_POSTFIX)) {
@@ -133,7 +217,44 @@
return true;
}
- public void testMetrics() {
+ private boolean runTestDirs(String[] testDirs) {
+ for (int i = 0; i < MAX_ITERATIONS; i++)
+ if (!runIteration(testDirs)) {
+ return false;
+ }
+ return true;
+ }
+
+ private void pushDoubleBuffering() {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ public void run() {
+ mWeb.setDoubleBuffering(mDoubleBuffering);
+ }
+ });
+ }
+
+ private void setScrollingTestingMode(final boolean scrolled) {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ public void run() {
+ mMovementSpinner.setSelection(scrolled ? 0 : 2);
+ }
+ });
+ }
+
+
+ private String[] mAnimTests = null;
+ private int mAnimTestNr = -1;
+ private boolean mDoubleBuffering = true;
+ private static final String[] ANIM_TEST_NAMES = {
+ "slow", "fast"
+ };
+ private static final String[][] ANIM_TESTS = {
+ {"scrolling", "replaceimages", "layers5", "layers1"},
+ {"slidingballs", "meter", "slidein", "fadespacing", "colorfade",
+ "mozilla", "movingtext", "diagball", "zoom", "imageslide"},
+ };
+
+ private boolean checkMedia() {
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)
@@ -141,27 +262,43 @@
Log.d(LOGTAG, "ARG Can't access sd card!");
// Can't read the SD card, fail and die!
getInstrumentation().sendStatus(1, null);
- return;
+ return false;
}
+ return true;
+ }
- // use mGraphs as a condition variable between the UI thread and
- // this(the testing) thread
- mActivity.setCallback(new ProfileCallback() {
- @Override
- public void profileCallback(RunData data) {
- Log.d(LOGTAG, "test completion callback");
- mStats.setData(data);
- synchronized (mStats) {
- mStats.notify();
+ public void testMetrics() {
+ setScrollingTestingMode(true);
+ if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) {
+ getInstrumentation().sendStatus(0, mStats.getBundle());
+ } else {
+ getInstrumentation().sendStatus(1, null);
+ }
+ }
+
+ private boolean runAnimationTests() {
+ for (int doubleBuffer = 0; doubleBuffer <= 1; doubleBuffer++) {
+ mDoubleBuffering = doubleBuffer == 1;
+ pushDoubleBuffering();
+ for (mAnimTestNr = 0; mAnimTestNr < ANIM_TESTS.length; mAnimTestNr++) {
+ mAnimTests = ANIM_TESTS[mAnimTestNr];
+ if (!runTestDirs(ANIM_TEST_DIRS)) {
+ return false;
}
}
- });
+ }
+ return true;
+ }
- for (int i = 0; i < MAX_ITERATIONS; i++)
- if (!runIteration()) {
- getInstrumentation().sendStatus(1, null);
- return;
- }
- getInstrumentation().sendStatus(0, mStats.getBundle());
+ public void testAnimations() {
+ // instead of autoscrolling, load each page until either an timer fires,
+ // or the animation signals complete via javascript
+ setScrollingTestingMode(false);
+
+ if (checkMedia() && runAnimationTests()) {
+ getInstrumentation().sendStatus(0, mStats.getBundle());
+ } else {
+ getInstrumentation().sendStatus(1, null);
+ }
}
}
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
index 9ea90f8..a3ae9be 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/PlaybackGraphs.java
@@ -80,7 +80,7 @@
for (int tileID = 1; tileID < frame.length; tileID++) {
TileData data = frame[tileID];
double coverage = viewportCoverage(frame[0], data);
- total += coverage * (data.isReady ? 1 : 0);
+ total += coverage * (data.isReady ? 100 : 0);
totalCount += coverage;
}
if (totalCount == 0) {
@@ -91,7 +91,7 @@
@Override
public double getMax() {
- return 1;
+ return 100;
}
@Override
@@ -108,6 +108,9 @@
}
public static double getPercentile(double sortedValues[], double ratioAbove) {
+ if (sortedValues.length == 0)
+ return -1;
+
double index = ratioAbove * (sortedValues.length - 1);
int intIndex = (int) Math.floor(index);
if (index == intIndex) {
@@ -118,6 +121,31 @@
+ sortedValues[intIndex + 1] * (alpha);
}
+ public static double getMean(double sortedValues[]) {
+ if (sortedValues.length == 0)
+ return -1;
+
+ double agg = 0;
+ for (double val : sortedValues) {
+ agg += val;
+ }
+ return agg / sortedValues.length;
+ }
+
+ public static double getStdDev(double sortedValues[]) {
+ if (sortedValues.length == 0)
+ return -1;
+
+ double agg = 0;
+ double sqrAgg = 0;
+ for (double val : sortedValues) {
+ agg += val;
+ sqrAgg += val*val;
+ }
+ double mean = agg / sortedValues.length;
+ return Math.sqrt((sqrAgg / sortedValues.length) - (mean * mean));
+ }
+
protected static StatGen[] Stats = new StatGen[] {
new StatGen() {
@Override
@@ -149,6 +177,26 @@
public int getLabelId() {
return R.string.percentile_75;
}
+ }, new StatGen() {
+ @Override
+ public double getValue(double[] sortedValues) {
+ return getStdDev(sortedValues);
+ }
+
+ @Override
+ public int getLabelId() {
+ return R.string.std_dev;
+ }
+ }, new StatGen() {
+ @Override
+ public double getValue(double[] sortedValues) {
+ return getMean(sortedValues);
+ }
+
+ @Override
+ public int getLabelId() {
+ return R.string.mean;
+ }
},
};
@@ -159,40 +207,47 @@
}
private ArrayList<ShapeDrawable> mShapes = new ArrayList<ShapeDrawable>();
- protected double[][] mStats = new double[Metrics.length][Stats.length];
+ protected final double[][] mStats = new double[Metrics.length][Stats.length];
protected HashMap<String, Double> mSingleStats;
+ private void gatherFrameMetric(int metricIndex, double metricValues[], RunData data) {
+ // create graph out of rectangles, one per frame
+ int lastBar = 0;
+ for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
+ TileData frame[] = data.frames[frameIndex];
+ int newBar = (frame[0].top + frame[0].bottom) / 2;
+
+ MetricGen s = Metrics[metricIndex];
+ double absoluteValue = s.getValue(frame);
+ double relativeValue = absoluteValue / s.getMax();
+ relativeValue = Math.min(1,relativeValue);
+ relativeValue = Math.max(0,relativeValue);
+ int rightPos = (int) (-BAR_WIDTH * metricIndex);
+ int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
+
+ ShapeDrawable graphBar = new ShapeDrawable();
+ graphBar.getPaint().setColor(Color.BLUE);
+ graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
+
+ mShapes.add(graphBar);
+ metricValues[frameIndex] = absoluteValue;
+ lastBar = newBar;
+ }
+ }
+
public void setData(RunData data) {
mShapes.clear();
double metricValues[] = new double[data.frames.length];
+ mSingleStats = data.singleStats;
+
if (data.frames.length == 0) {
return;
}
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
- // create graph out of rectangles, one per frame
- int lastBar = 0;
- for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
- TileData frame[] = data.frames[frameIndex];
- int newBar = (frame[0].top + frame[0].bottom) / 2;
-
- MetricGen s = Metrics[metricIndex];
- double absoluteValue = s.getValue(frame);
- double relativeValue = absoluteValue / s.getMax();
- relativeValue = Math.min(1,relativeValue);
- relativeValue = Math.max(0,relativeValue);
- int rightPos = (int) (-BAR_WIDTH * metricIndex);
- int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
-
- ShapeDrawable graphBar = new ShapeDrawable();
- graphBar.getPaint().setColor(Color.BLUE);
- graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
-
- mShapes.add(graphBar);
- metricValues[frameIndex] = absoluteValue;
- lastBar = newBar;
- }
+ // calculate metric based on list of frames
+ gatherFrameMetric(metricIndex, metricValues, data);
// store aggregate statistics per metric (median, and similar)
Arrays.sort(metricValues);
@@ -200,8 +255,6 @@
mStats[metricIndex][statIndex] =
Stats[statIndex].getValue(metricValues);
}
-
- mSingleStats = data.singleStats;
}
}
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
index d38d006..2e77157 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfileActivity.java
@@ -22,11 +22,12 @@
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
-import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
@@ -49,6 +50,8 @@
*/
public class ProfileActivity extends Activity {
+ private static final int TIMED_RECORD_MILLIS = 2000;
+
public interface ProfileCallback {
public void profileCallback(RunData data);
}
@@ -65,6 +68,7 @@
LoggingWebViewClient mLoggingWebViewClient = new LoggingWebViewClient();
AutoLoggingWebViewClient mAutoLoggingWebViewClient = new AutoLoggingWebViewClient();
+ TimedLoggingWebViewClient mTimedLoggingWebViewClient = new TimedLoggingWebViewClient();
private enum TestingState {
NOT_TESTING,
@@ -93,18 +97,18 @@
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
String movementStr = parent.getItemAtPosition(position).toString();
- if (movementStr == getResources().getString(
- R.string.movement_auto_scroll)
- || movementStr == getResources().getString(
- R.string.movement_auto_fling)) {
+ if (movementStr == getResources().getString(R.string.movement_auto_scroll)) {
mWeb.setWebViewClient(mAutoLoggingWebViewClient);
mCaptureButton.setEnabled(false);
mVelocitySpinner.setEnabled(true);
- } else if (movementStr == getResources().getString(
- R.string.movement_manual)) {
+ } else if (movementStr == getResources().getString(R.string.movement_manual)) {
mWeb.setWebViewClient(mLoggingWebViewClient);
mCaptureButton.setEnabled(true);
mVelocitySpinner.setEnabled(false);
+ } else if (movementStr == getResources().getString(R.string.movement_timed)) {
+ mWeb.setWebViewClient(mTimedLoggingWebViewClient);
+ mCaptureButton.setEnabled(false);
+ mVelocitySpinner.setEnabled(false);
}
}
@@ -124,15 +128,19 @@
super.onPageStarted(view, url, favicon);
mUrl.setText(url);
}
- }
-
- private class AutoLoggingWebViewClient extends LoggingWebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
view.requestFocus();
+ ((ProfiledWebView)view).onPageFinished();
+ }
+ }
+ private class AutoLoggingWebViewClient extends LoggingWebViewClient {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
startViewProfiling(true);
}
@@ -143,6 +151,32 @@
}
}
+ private class TimedLoggingWebViewClient extends LoggingWebViewClient {
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ startViewProfiling(false);
+
+ // after a fixed time after page finished, stop testing
+ new CountDownTimer(TIMED_RECORD_MILLIS, TIMED_RECORD_MILLIS) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ }
+
+ @Override
+ public void onFinish() {
+ mWeb.stopScrollTest();
+ }
+ }.start();
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ setTestingState(TestingState.PRE_TESTING);
+ }
+ }
+
private class StoreFileTask extends
AsyncTask<Pair<String, RunData>, Void, Void> {
@@ -178,11 +212,13 @@
mMovementSpinner.setEnabled(false);
break;
case START_TESTING:
+ mCaptureButton.setChecked(true);
mUrl.setBackgroundResource(R.color.background_start_testing);
mInspectButton.setEnabled(false);
mMovementSpinner.setEnabled(false);
break;
case STOP_TESTING:
+ mCaptureButton.setChecked(false);
mUrl.setBackgroundResource(R.color.background_stop_testing);
break;
case SAVED_TESTING:
@@ -195,7 +231,6 @@
/** auto - automatically scroll. */
private void startViewProfiling(boolean auto) {
// toggle capture button to indicate capture state to user
- mCaptureButton.setChecked(true);
mWeb.startScrollTest(mCallback, auto);
setTestingState(TestingState.START_TESTING);
}
@@ -217,7 +252,7 @@
public void profileCallback(RunData data) {
new StoreFileTask().execute(new Pair<String, RunData>(
TEMP_FILENAME, data));
- mCaptureButton.setChecked(false);
+ Log.d("ProfileActivity", "stored " + data.frames.length + " frames in file");
setTestingState(TestingState.STOP_TESTING);
}
});
@@ -245,8 +280,8 @@
// Movement spinner
String content[] = {
getResources().getString(R.string.movement_auto_scroll),
- getResources().getString(R.string.movement_auto_fling),
- getResources().getString(R.string.movement_manual)
+ getResources().getString(R.string.movement_manual),
+ getResources().getString(R.string.movement_timed)
};
adapter = new ArrayAdapter<CharSequence>(this,
android.R.layout.simple_spinner_item, content);
@@ -270,13 +305,7 @@
});
// Custom profiling WebView
- WebSettings settings = mWeb.getSettings();
- settings.setJavaScriptEnabled(true);
- settings.setSupportZoom(true);
- settings.setEnableSmoothTransition(true);
- settings.setBuiltInZoomControls(true);
- settings.setLoadWithOverviewMode(true);
- settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does
+ mWeb.init(this);
mWeb.setWebViewClient(new LoggingWebViewClient());
// URL text entry
diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
index 83f1668..a706f78 100644
--- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
+++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java
@@ -20,21 +20,28 @@
import android.os.CountDownTimer;
import android.util.AttributeSet;
import android.util.Log;
+import android.webkit.WebSettings;
import android.webkit.WebView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
import com.test.tilebenchmark.ProfileActivity.ProfileCallback;
import com.test.tilebenchmark.RunData.TileData;
public class ProfiledWebView extends WebView {
+ private static final String LOGTAG = "ProfiledWebView";
+
private int mSpeed;
private boolean mIsTesting = false;
private boolean mIsScrolling = false;
private ProfileCallback mCallback;
private long mContentInvalMillis;
- private boolean mHadToBeForced = false;
private static final int LOAD_STALL_MILLIS = 2000; // nr of millis after load,
// before test is forced
+ private double mLoadTime;
+ private double mAnimationTime;
public ProfiledWebView(Context context) {
super(context);
@@ -53,6 +60,39 @@
super(context, attrs, defStyle, privateBrowsing);
}
+ private class JavaScriptInterface {
+ Context mContext;
+
+ /** Instantiate the interface and set the context */
+ JavaScriptInterface(Context c) {
+ mContext = c;
+ }
+
+ /** Show a toast from the web page */
+ public void animationComplete() {
+ Toast.makeText(mContext, "Animation complete!", Toast.LENGTH_SHORT).show();
+ //Log.d(LOGTAG, "anim complete");
+ mAnimationTime = System.currentTimeMillis();
+ }
+ }
+
+ public void init(Context c) {
+ WebSettings settings = getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setSupportZoom(true);
+ settings.setEnableSmoothTransition(true);
+ settings.setBuiltInZoomControls(true);
+ settings.setLoadWithOverviewMode(true);
+ settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does
+ addJavascriptInterface(new JavaScriptInterface(c), "Android");
+ mAnimationTime = 0;
+ mLoadTime = 0;
+ }
+
+ public void onPageFinished() {
+ mLoadTime = System.currentTimeMillis();
+ }
+
@Override
protected void onDraw(android.graphics.Canvas canvas) {
if (mIsTesting && mIsScrolling) {
@@ -72,9 +112,12 @@
* scrolling, invalidate all content and redraw it, measuring time taken.
*/
public void startScrollTest(ProfileCallback callback, boolean autoScrolling) {
- mIsScrolling = autoScrolling;
mCallback = callback;
mIsTesting = false;
+ mIsScrolling = false;
+ WebSettings settings = getSettings();
+ settings.setProperty("tree_updates", "0");
+
if (autoScrolling) {
// after a while, force it to start even if the pages haven't swapped
@@ -86,13 +129,18 @@
@Override
public void onFinish() {
// invalidate all content, and kick off redraw
+ Log.d("ProfiledWebView",
+ "kicking off test with callback registration, and tile discard...");
registerPageSwapCallback();
discardAllTextures();
invalidate();
-
+ mIsScrolling = true;
mContentInvalMillis = System.currentTimeMillis();
}
}.start();
+ } else {
+ mIsTesting = true;
+ tileProfilingStart();
}
}
@@ -102,13 +150,35 @@
*/
@Override
protected void pageSwapCallback(boolean startAnim) {
- mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
- super.pageSwapCallback(startAnim);
- Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis
- + "millis");
- mIsTesting = true;
- invalidate(); // ensure a redraw so that auto-scrolling can occur
- tileProfilingStart();
+ if (!mIsTesting && mIsScrolling) {
+ // kick off testing
+ mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
+ super.pageSwapCallback(startAnim);
+ Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis + "millis");
+ mIsTesting = true;
+ invalidate(); // ensure a redraw so that auto-scrolling can occur
+ tileProfilingStart();
+ }
+ }
+
+ private double animFramerate() {
+ WebSettings settings = getSettings();
+ String updatesString = settings.getProperty("tree_updates");
+ int updates = (updatesString == null) ? -1 : Integer.parseInt(updatesString);
+
+ double animationTime;
+ if (mAnimationTime == 0) {
+ animationTime = System.currentTimeMillis() - mLoadTime;
+ } else {
+ animationTime = mAnimationTime - mLoadTime;
+ }
+
+ return updates * 1000 / animationTime;
+ }
+
+ public void setDoubleBuffering(boolean useDoubleBuffering) {
+ WebSettings settings = getSettings();
+ settings.setProperty("use_double_buffering", useDoubleBuffering ? "true" : "false");
}
/*
@@ -127,11 +197,12 @@
// record the time spent (before scrolling) rendering the page
data.singleStats.put(getResources().getString(R.string.render_millis),
(double)mContentInvalMillis);
- // record if the page render timed out
- Log.d("ProfiledWebView", "hadtobeforced = " + mHadToBeForced);
- data.singleStats.put(getResources().getString(R.string.render_stalls),
- mHadToBeForced ? 1.0 : 0.0);
- mHadToBeForced = false;
+
+ // record framerate
+ double framerate = animFramerate();
+ Log.d(LOGTAG, "anim framerate was "+framerate);
+ data.singleStats.put(getResources().getString(R.string.animation_framerate),
+ framerate);
for (int frame = 0; frame < data.frames.length; frame++) {
data.frames[frame] = new TileData[
@@ -159,6 +230,8 @@
@Override
public void loadUrl(String url) {
+ mAnimationTime = 0;
+ mLoadTime = 0;
if (!url.startsWith("http://") && !url.startsWith("file://")) {
url = "http://" + url;
}
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index f0c215e..6ce665b 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -1084,12 +1084,17 @@
if (out) out->density = ResTable_config::DENSITY_HIGH;
return true;
}
-
+
if (strcmp(name, "xhdpi") == 0) {
- if (out) out->density = ResTable_config::DENSITY_MEDIUM*2;
+ if (out) out->density = ResTable_config::DENSITY_XHIGH;
return true;
}
-
+
+ if (strcmp(name, "xxhdpi") == 0) {
+ if (out) out->density = ResTable_config::DENSITY_XXHIGH;
+ return true;
+ }
+
char* c = (char*)name;
while (*c >= '0' && *c <= '9') {
c++;