diff options
92 files changed, 3709 insertions, 1657 deletions
diff --git a/api/current.txt b/api/current.txt index 7c05ed6..85f3633 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6791,16 +6791,19 @@ package android.database { 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 { @@ -7785,7 +7788,7 @@ package android.gesture { 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 +8567,7 @@ package android.graphics { field public int bytesPerPixel; } - public class PixelXorXfermode extends android.graphics.Xfermode { + public deprecated class PixelXorXfermode extends android.graphics.Xfermode { ctor public PixelXorXfermode(int); } @@ -17560,6 +17563,7 @@ package android.provider { 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 @@ -23817,8 +23821,9 @@ package android.view { 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 +26676,7 @@ package android.widget { 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/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index cc3219b..0debb84 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -1034,8 +1034,11 @@ public abstract class ContentResolver { * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}. * By default, CursorAdapter objects will get this notification. * - * @param uri - * @param observer The observer that originated the change, may be <code>null</null> + * @param uri The uri of the content that was changed. + * @param observer The observer that originated the change, may be <code>null</null>. + * The observer that originated the change will only receive the notification if it + * has requested to receive self-change notifications by implementing + * {@link ContentObserver#deliverSelfNotifications()} to return true. */ public void notifyChange(Uri uri, ContentObserver observer) { notifyChange(uri, observer, true /* sync to network */); @@ -1046,8 +1049,11 @@ public abstract class ContentResolver { * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}. * By default, CursorAdapter objects will get this notification. * - * @param uri - * @param observer The observer that originated the change, may be <code>null</null> + * @param uri The uri of the content that was changed. + * @param observer The observer that originated the change, may be <code>null</null>. + * The observer that originated the change will only receive the notification if it + * has requested to receive self-change notifications by implementing + * {@link ContentObserver#deliverSelfNotifications()} to return true. * @param syncToNetwork If true, attempt to sync the change to the network. */ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index 0e83dc0..fc4c262 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -176,7 +176,7 @@ public final class ContentService extends IContentService.Stub { for (int i=0; i<numCalls; i++) { ObserverCall oc = calls.get(i); try { - oc.mObserver.onChange(oc.mSelfNotify); + oc.mObserver.onChange(oc.mSelfChange, uri); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri); } @@ -218,13 +218,12 @@ public final class ContentService extends IContentService.Stub { public static final class ObserverCall { final ObserverNode mNode; final IContentObserver mObserver; - final boolean mSelfNotify; + final boolean mSelfChange; - ObserverCall(ObserverNode node, IContentObserver observer, - boolean selfNotify) { + ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) { mNode = node; mObserver = observer; - mSelfNotify = selfNotify; + mSelfChange = selfChange; } } @@ -668,7 +667,7 @@ public final class ContentService extends IContentService.Stub { } private void collectMyObserversLocked(boolean leaf, IContentObserver observer, - boolean selfNotify, ArrayList<ObserverCall> calls) { + boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) { int N = mObservers.size(); IBinder observerBinder = observer == null ? null : observer.asBinder(); for (int i = 0; i < N; i++) { @@ -676,28 +675,29 @@ public final class ContentService extends IContentService.Stub { // Don't notify the observer if it sent the notification and isn't interesed // in self notifications - if (entry.observer.asBinder() == observerBinder && !selfNotify) { + boolean selfChange = (entry.observer.asBinder() == observerBinder); + if (selfChange && !observerWantsSelfNotifications) { continue; } // Make sure the observer is interested in the notification if (leaf || (!leaf && entry.notifyForDescendents)) { - calls.add(new ObserverCall(this, entry.observer, selfNotify)); + calls.add(new ObserverCall(this, entry.observer, selfChange)); } } } public void collectObserversLocked(Uri uri, int index, IContentObserver observer, - boolean selfNotify, ArrayList<ObserverCall> calls) { + boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) { String segment = null; int segmentCount = countUriSegments(uri); if (index >= segmentCount) { // This is the leaf node, notify all observers - collectMyObserversLocked(true, observer, selfNotify, calls); + collectMyObserversLocked(true, observer, observerWantsSelfNotifications, calls); } else if (index < segmentCount){ segment = getUriSegment(uri, index); // Notify any observers at this level who are interested in descendents - collectMyObserversLocked(false, observer, selfNotify, calls); + collectMyObserversLocked(false, observer, observerWantsSelfNotifications, calls); } int N = mChildren.size(); @@ -705,7 +705,8 @@ public final class ContentService extends IContentService.Stub { ObserverNode node = mChildren.get(i); if (segment == null || node.mName.equals(segment)) { // We found the child, - node.collectObserversLocked(uri, index + 1, observer, selfNotify, calls); + node.collectObserversLocked(uri, index + 1, + observer, observerWantsSelfNotifications, calls); if (segment != null) { break; } diff --git a/core/java/android/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 @@ public abstract class AbstractCursor implements CrossProcessCursor { */ protected void onChange(boolean selfChange) { synchronized (mSelfObserverLock) { - mContentObservable.dispatchChange(selfChange); + mContentObservable.dispatchChange(selfChange, null); if (mNotifyUri != null && selfChange) { mContentResolver.notifyChange(mNotifyUri, mSelfObserver); } diff --git a/core/java/android/database/ContentObservable.java b/core/java/android/database/ContentObservable.java index 8d7b7c5..7692bb3 100644 --- a/core/java/android/database/ContentObservable.java +++ b/core/java/android/database/ContentObservable.java @@ -16,40 +16,75 @@ package android.database; +import android.net.Uri; + /** - * A specialization of Observable for ContentObserver that provides methods for - * invoking the various callback methods of ContentObserver. + * A specialization of {@link Observable} for {@link ContentObserver} + * that provides methods for sending notifications to a list of + * {@link ContentObserver} objects. */ public class ContentObservable extends Observable<ContentObserver> { - + // Even though the generic method defined in Observable would be perfectly + // fine on its own, we can't delete this overridden method because it would + // potentially break binary compatibility with existing applications. @Override public void registerObserver(ContentObserver observer) { super.registerObserver(observer); } /** - * invokes dispatchUpdate on each observer, unless the observer doesn't want - * self-notifications and the update is from a self-notification - * @param selfChange + * Invokes {@link ContentObserver#dispatchChange(boolean)} on each observer. + * <p> + * If <code>selfChange</code> is true, only delivers the notification + * to the observer if it has indicated that it wants to receive self-change + * notifications by implementing {@link ContentObserver#deliverSelfNotifications} + * to return true. + * </p> + * + * @param selfChange True if this is a self-change notification. + * + * @deprecated Use {@link #dispatchChange(boolean, Uri)} instead. */ + @Deprecated public void dispatchChange(boolean selfChange) { + dispatchChange(selfChange, null); + } + + /** + * Invokes {@link ContentObserver#dispatchChange(boolean, Uri)} on each observer. + * Includes the changed content Uri when available. + * <p> + * If <code>selfChange</code> is true, only delivers the notification + * to the observer if it has indicated that it wants to receive self-change + * notifications by implementing {@link ContentObserver#deliverSelfNotifications} + * to return true. + * </p> + * + * @param selfChange True if this is a self-change notification. + * @param uri The Uri of the changed content, or null if unknown. + */ + public void dispatchChange(boolean selfChange, Uri uri) { synchronized(mObservers) { for (ContentObserver observer : mObservers) { if (!selfChange || observer.deliverSelfNotifications()) { - observer.dispatchChange(selfChange); + observer.dispatchChange(selfChange, uri); } } } } /** - * invokes onChange on each observer - * @param selfChange + * Invokes {@link ContentObserver#onChange} on each observer. + * + * @param selfChange True if this is a self-change notification. + * + * @deprecated Use {@link #dispatchChange} instead. */ + @Deprecated public void notifyChange(boolean selfChange) { synchronized(mObservers) { for (ContentObserver observer : mObservers) { - observer.onChange(selfChange); + observer.onChange(selfChange, null); } } } diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java index 3b829a3..e4fbc28 100644 --- a/core/java/android/database/ContentObserver.java +++ b/core/java/android/database/ContentObserver.java @@ -16,65 +16,23 @@ package android.database; +import android.net.Uri; import android.os.Handler; /** - * Receives call backs for changes to content. Must be implemented by objects which are added - * to a {@link ContentObservable}. + * Receives call backs for changes to content. + * Must be implemented by objects which are added to a {@link ContentObservable}. */ public abstract class ContentObserver { + private final Object mLock = new Object(); + private Transport mTransport; // guarded by mLock - private Transport mTransport; - - // Protects mTransport - private Object lock = new Object(); - - /* package */ Handler mHandler; - - private final class NotificationRunnable implements Runnable { - - private boolean mSelf; - - public NotificationRunnable(boolean self) { - mSelf = self; - } - - public void run() { - ContentObserver.this.onChange(mSelf); - } - } - - private static final class Transport extends IContentObserver.Stub { - ContentObserver mContentObserver; - - public Transport(ContentObserver contentObserver) { - mContentObserver = contentObserver; - } - - public boolean deliverSelfNotifications() { - ContentObserver contentObserver = mContentObserver; - if (contentObserver != null) { - return contentObserver.deliverSelfNotifications(); - } - return false; - } - - public void onChange(boolean selfChange) { - ContentObserver contentObserver = mContentObserver; - if (contentObserver != null) { - contentObserver.dispatchChange(selfChange); - } - } - - public void releaseContentObserver() { - mContentObserver = null; - } - } + Handler mHandler; /** - * onChange() will happen on the provider Handler. + * Creates a content observer. * - * @param handler The handler to run {@link #onChange} on. + * @param handler The handler to run {@link #onChange} on, or null if none. */ public ContentObserver(Handler handler) { mHandler = handler; @@ -86,7 +44,7 @@ public abstract class ContentObserver { * {@hide} */ public IContentObserver getContentObserver() { - synchronized(lock) { + synchronized (mLock) { if (mTransport == null) { mTransport = new Transport(this); } @@ -101,8 +59,8 @@ public abstract class ContentObserver { * {@hide} */ public IContentObserver releaseContentObserver() { - synchronized(lock) { - Transport oldTransport = mTransport; + synchronized (mLock) { + final Transport oldTransport = mTransport; if (oldTransport != null) { oldTransport.releaseContentObserver(); mTransport = null; @@ -112,27 +70,134 @@ public abstract class ContentObserver { } /** - * Returns true if this observer is interested in notifications for changes - * made through the cursor the observer is registered with. + * Returns true if this observer is interested receiving self-change notifications. + * + * Subclasses should override this method to indicate whether the observer + * is interested in receiving notifications for changes that it made to the + * content itself. + * + * @return True if self-change notifications should be delivered to the observer. */ public boolean deliverSelfNotifications() { return false; } /** - * This method is called when a change occurs to the cursor that - * is being observed. - * - * @param selfChange true if the update was caused by a call to <code>commit</code> on the - * cursor that is being observed. + * This method is called when a content change occurs. + * <p> + * Subclasses should override this method to handle content changes. + * </p> + * + * @param selfChange True if this is a self-change notification. */ - public void onChange(boolean selfChange) {} + public void onChange(boolean selfChange) { + // Do nothing. Subclass should override. + } + /** + * This method is called when a content change occurs. + * Includes the changed content Uri when available. + * <p> + * Subclasses should override this method to handle content changes. + * To ensure correct operation on older versions of the framework that + * did not provide a Uri argument, applications should also implement + * the {@link #onChange(boolean)} overload of this method whenever they + * implement the {@link #onChange(boolean, Uri)} overload. + * </p><p> + * Example implementation: + * <pre><code> + * // Implement the onChange(boolean) method to delegate the change notification to + * // the onChange(boolean, Uri) method to ensure correct operation on older versions + * // of the framework that did not have the onChange(boolean, Uri) method. + * {@literal @Override} + * public void onChange(boolean selfChange) { + * onChange(selfChange, null); + * } + * + * // Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument. + * {@literal @Override} + * public void onChange(boolean selfChange, Uri uri) { + * // Handle change. + * } + * </code></pre> + * </p> + * + * @param selfChange True if this is a self-change notification. + * @param uri The Uri of the changed content, or null if unknown. + */ + public void onChange(boolean selfChange, Uri uri) { + onChange(selfChange); + } + + /** + * Dispatches a change notification to the observer. + * <p> + * If a {@link Handler} was supplied to the {@link ContentObserver} constructor, + * then a call to the {@link #onChange} method is posted to the handler's message queue. + * Otherwise, the {@link #onChange} method is invoked immediately on this thread. + * </p> + * + * @param selfChange True if this is a self-change notification. + * + * @deprecated Use {@link #dispatchChange(boolean, Uri)} instead. + */ + @Deprecated public final void dispatchChange(boolean selfChange) { + dispatchChange(selfChange, null); + } + + /** + * Dispatches a change notification to the observer. + * Includes the changed content Uri when available. + * <p> + * If a {@link Handler} was supplied to the {@link ContentObserver} constructor, + * then a call to the {@link #onChange} method is posted to the handler's message queue. + * Otherwise, the {@link #onChange} method is invoked immediately on this thread. + * </p> + * + * @param selfChange True if this is a self-change notification. + * @param uri The Uri of the changed content, or null if unknown. + */ + public final void dispatchChange(boolean selfChange, Uri uri) { if (mHandler == null) { - onChange(selfChange); + onChange(selfChange, uri); } else { - mHandler.post(new NotificationRunnable(selfChange)); + mHandler.post(new NotificationRunnable(selfChange, uri)); + } + } + + private final class NotificationRunnable implements Runnable { + private final boolean mSelfChange; + private final Uri mUri; + + public NotificationRunnable(boolean selfChange, Uri uri) { + mSelfChange = selfChange; + mUri = uri; + } + + @Override + public void run() { + ContentObserver.this.onChange(mSelfChange, mUri); + } + } + + private static final class Transport extends IContentObserver.Stub { + private ContentObserver mContentObserver; + + public Transport(ContentObserver contentObserver) { + mContentObserver = contentObserver; + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + ContentObserver contentObserver = mContentObserver; + if (contentObserver != null) { + contentObserver.dispatchChange(selfChange, uri); + } + } + + public void releaseContentObserver() { + mContentObserver = null; } } } diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java index aa0f61e..167278a 100644 --- a/core/java/android/database/CursorToBulkCursorAdaptor.java +++ b/core/java/android/database/CursorToBulkCursorAdaptor.java @@ -16,6 +16,7 @@ package android.database; +import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -78,9 +79,9 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } @Override - public void onChange(boolean selfChange) { + public void onChange(boolean selfChange, Uri uri) { try { - mRemote.onChange(selfChange); + mRemote.onChange(selfChange, uri); } catch (RemoteException ex) { // Do nothing, the far side is dead } diff --git a/core/java/android/database/DataSetObservable.java b/core/java/android/database/DataSetObservable.java index 51c72c1..ca77a13 100644 --- a/core/java/android/database/DataSetObservable.java +++ b/core/java/android/database/DataSetObservable.java @@ -17,13 +17,15 @@ package android.database; /** - * A specialization of Observable for DataSetObserver that provides methods for - * invoking the various callback methods of DataSetObserver. + * A specialization of {@link Observable} for {@link DataSetObserver} + * that provides methods for sending notifications to a list of + * {@link DataSetObserver} objects. */ public class DataSetObservable extends Observable<DataSetObserver> { /** - * Invokes onChanged on each observer. Called when the data set being observed has - * changed, and which when read contains the new state of the data. + * Invokes {@link DataSetObserver#onChanged} on each observer. + * Called when the contents of the data set have changed. The recipient + * will obtain the new contents the next time it queries the data set. */ public void notifyChanged() { synchronized(mObservers) { @@ -38,8 +40,9 @@ public class DataSetObservable extends Observable<DataSetObserver> { } /** - * Invokes onInvalidated on each observer. Called when the data set being monitored - * has changed such that it is no longer valid. + * Invokes {@link DataSetObserver#onInvalidated} on each observer. + * Called when the data set is no longer valid and cannot be queried again, + * such as when the data set has been closed. */ public void notifyInvalidated() { synchronized (mObservers) { diff --git a/core/java/android/database/IContentObserver.aidl b/core/java/android/database/IContentObserver.aidl index ac2f975..13aff05 100755 --- a/core/java/android/database/IContentObserver.aidl +++ b/core/java/android/database/IContentObserver.aidl @@ -17,6 +17,8 @@ package android.database; +import android.net.Uri; + /** * @hide */ @@ -27,5 +29,5 @@ interface IContentObserver * observed. selfUpdate is true if the update was caused by a call to * commit on the cursor that is being observed. */ - oneway void onChange(boolean selfUpdate); + oneway void onChange(boolean selfUpdate, in Uri uri); } diff --git a/core/java/android/database/Observable.java b/core/java/android/database/Observable.java index b6fecab..aff32db 100644 --- a/core/java/android/database/Observable.java +++ b/core/java/android/database/Observable.java @@ -19,7 +19,12 @@ package android.database; import java.util.ArrayList; /** - * Provides methods for (un)registering arbitrary observers in an ArrayList. + * Provides methods for registering or unregistering arbitrary observers in an {@link ArrayList}. + * + * This abstract class is intended to be subclassed and specialized to maintain + * a registry of observers of specific types and dispatch notifications to them. + * + * @param T The observer type. */ public abstract class Observable<T> { /** @@ -66,13 +71,13 @@ public abstract class Observable<T> { mObservers.remove(index); } } - + /** - * Remove all registered observer + * Remove all registered observers. */ public void unregisterAll() { synchronized(mObservers) { mObservers.clear(); - } + } } } diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index e8f60b4..7a1ef66 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -102,6 +102,15 @@ public class NetworkStats implements Parcelable { this.operations = operations; } + public boolean isNegative() { + return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0; + } + + public boolean isEmpty() { + return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0 + && operations == 0; + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); @@ -343,6 +352,7 @@ public class NetworkStats implements Parcelable { * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, * since operation counts are at data layer. */ + @Deprecated public void spliceOperationsFrom(NetworkStats stats) { for (int i = 0; i < size; i++) { final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]); @@ -397,7 +407,7 @@ public class NetworkStats implements Parcelable { * Return total of all fields represented by this snapshot object. */ public Entry getTotal(Entry recycle) { - return getTotal(recycle, null, UID_ALL); + return getTotal(recycle, null, UID_ALL, false); } /** @@ -405,7 +415,7 @@ public class NetworkStats implements Parcelable { * the requested {@link #uid}. */ public Entry getTotal(Entry recycle, int limitUid) { - return getTotal(recycle, null, limitUid); + return getTotal(recycle, null, limitUid, false); } /** @@ -413,7 +423,11 @@ public class NetworkStats implements Parcelable { * the requested {@link #iface}. */ public Entry getTotal(Entry recycle, HashSet<String> limitIface) { - return getTotal(recycle, limitIface, UID_ALL); + return getTotal(recycle, limitIface, UID_ALL, false); + } + + public Entry getTotalIncludingTags(Entry recycle) { + return getTotal(recycle, null, UID_ALL, true); } /** @@ -423,7 +437,8 @@ public class NetworkStats implements Parcelable { * @param limitIface Set of {@link #iface} to include in total; or {@code * null} to include all ifaces. */ - private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) { + private Entry getTotal( + Entry recycle, HashSet<String> limitIface, int limitUid, boolean includeTags) { final Entry entry = recycle != null ? recycle : new Entry(); entry.iface = IFACE_ALL; @@ -442,7 +457,7 @@ public class NetworkStats implements Parcelable { if (matchesUid && matchesIface) { // skip specific tags, since already counted in TAG_NONE - if (tag[i] != TAG_NONE) continue; + if (tag[i] != TAG_NONE && !includeTags) continue; entry.rxBytes += rxBytes[i]; entry.rxPackets += rxPackets[i]; @@ -460,7 +475,7 @@ public class NetworkStats implements Parcelable { * time, and that none of them have disappeared. */ public NetworkStats subtract(NetworkStats right) { - return subtract(this, right, null); + return subtract(this, right, null, null); } /** @@ -471,12 +486,12 @@ public class NetworkStats implements Parcelable { * If counters have rolled backwards, they are clamped to {@code 0} and * reported to the given {@link NonMonotonicObserver}. */ - public static NetworkStats subtract( - NetworkStats left, NetworkStats right, NonMonotonicObserver observer) { + public static <C> NetworkStats subtract( + NetworkStats left, NetworkStats right, NonMonotonicObserver<C> observer, C cookie) { long deltaRealtime = left.elapsedRealtime - right.elapsedRealtime; if (deltaRealtime < 0) { if (observer != null) { - observer.foundNonMonotonic(left, -1, right, -1); + observer.foundNonMonotonic(left, -1, right, -1, cookie); } deltaRealtime = 0; } @@ -510,7 +525,7 @@ public class NetworkStats implements Parcelable { if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 || entry.txPackets < 0 || entry.operations < 0) { if (observer != null) { - observer.foundNonMonotonic(left, i, right, j); + observer.foundNonMonotonic(left, i, right, j, cookie); } entry.rxBytes = Math.max(entry.rxBytes, 0); entry.rxPackets = Math.max(entry.rxPackets, 0); @@ -663,8 +678,8 @@ public class NetworkStats implements Parcelable { } }; - public interface NonMonotonicObserver { + public interface NonMonotonicObserver<C> { public void foundNonMonotonic( - NetworkStats left, int leftIndex, NetworkStats right, int rightIndex); + NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie); } } diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index 8c01331..faf8a3f 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -26,16 +26,18 @@ import static android.net.NetworkStatsHistory.DataStreamUtils.writeVarLongArray; import static android.net.NetworkStatsHistory.Entry.UNKNOWN; import static android.net.NetworkStatsHistory.ParcelUtils.readLongArray; import static android.net.NetworkStatsHistory.ParcelUtils.writeLongArray; +import static com.android.internal.util.ArrayUtils.total; import android.os.Parcel; import android.os.Parcelable; import android.util.MathUtils; +import com.android.internal.util.IndentingPrintWriter; + import java.io.CharArrayWriter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.PrintWriter; import java.net.ProtocolException; import java.util.Arrays; import java.util.Random; @@ -74,6 +76,7 @@ public class NetworkStatsHistory implements Parcelable { private long[] txBytes; private long[] txPackets; private long[] operations; + private long totalBytes; public static class Entry { public static final long UNKNOWN = -1; @@ -106,6 +109,12 @@ public class NetworkStatsHistory implements Parcelable { if ((fields & FIELD_TX_PACKETS) != 0) txPackets = new long[initialSize]; if ((fields & FIELD_OPERATIONS) != 0) operations = new long[initialSize]; bucketCount = 0; + totalBytes = 0; + } + + public NetworkStatsHistory(NetworkStatsHistory existing, long bucketDuration) { + this(bucketDuration, existing.estimateResizeBuckets(bucketDuration)); + recordEntireHistory(existing); } public NetworkStatsHistory(Parcel in) { @@ -118,6 +127,7 @@ public class NetworkStatsHistory implements Parcelable { txPackets = readLongArray(in); operations = readLongArray(in); bucketCount = bucketStart.length; + totalBytes = in.readLong(); } /** {@inheritDoc} */ @@ -130,6 +140,7 @@ public class NetworkStatsHistory implements Parcelable { writeLongArray(out, txBytes, bucketCount); writeLongArray(out, txPackets, bucketCount); writeLongArray(out, operations, bucketCount); + out.writeLong(totalBytes); } public NetworkStatsHistory(DataInputStream in) throws IOException { @@ -144,6 +155,7 @@ public class NetworkStatsHistory implements Parcelable { txPackets = new long[bucketStart.length]; operations = new long[bucketStart.length]; bucketCount = bucketStart.length; + totalBytes = total(rxBytes) + total(txBytes); break; } case VERSION_ADD_PACKETS: @@ -158,6 +170,7 @@ public class NetworkStatsHistory implements Parcelable { txPackets = readVarLongArray(in); operations = readVarLongArray(in); bucketCount = bucketStart.length; + totalBytes = total(rxBytes) + total(txBytes); break; } default: { @@ -208,6 +221,13 @@ public class NetworkStatsHistory implements Parcelable { } /** + * Return total bytes represented by this history. + */ + public long getTotalBytes() { + return totalBytes; + } + + /** * Return index of bucket that contains or is immediately before the * requested time. */ @@ -266,13 +286,16 @@ public class NetworkStatsHistory implements Parcelable { * distribute across internal buckets, creating new buckets as needed. */ public void recordData(long start, long end, NetworkStats.Entry entry) { - if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 || entry.txPackets < 0 - || entry.operations < 0) { + long rxBytes = entry.rxBytes; + long rxPackets = entry.rxPackets; + long txBytes = entry.txBytes; + long txPackets = entry.txPackets; + long operations = entry.operations; + + if (entry.isNegative()) { throw new IllegalArgumentException("tried recording negative data"); } - if (entry.rxBytes == 0 && entry.rxPackets == 0 && entry.txBytes == 0 && entry.txPackets == 0 - && entry.operations == 0) { - // nothing to record; skip + if (entry.isEmpty()) { return; } @@ -295,21 +318,23 @@ public class NetworkStatsHistory implements Parcelable { if (overlap <= 0) continue; // integer math each time is faster than floating point - final long fracRxBytes = entry.rxBytes * overlap / duration; - final long fracRxPackets = entry.rxPackets * overlap / duration; - final long fracTxBytes = entry.txBytes * overlap / duration; - final long fracTxPackets = entry.txPackets * overlap / duration; - final long fracOperations = entry.operations * overlap / duration; + final long fracRxBytes = rxBytes * overlap / duration; + final long fracRxPackets = rxPackets * overlap / duration; + final long fracTxBytes = txBytes * overlap / duration; + final long fracTxPackets = txPackets * overlap / duration; + final long fracOperations = operations * overlap / duration; addLong(activeTime, i, overlap); - addLong(rxBytes, i, fracRxBytes); entry.rxBytes -= fracRxBytes; - addLong(rxPackets, i, fracRxPackets); entry.rxPackets -= fracRxPackets; - addLong(txBytes, i, fracTxBytes); entry.txBytes -= fracTxBytes; - addLong(txPackets, i, fracTxPackets); entry.txPackets -= fracTxPackets; - addLong(operations, i, fracOperations); entry.operations -= fracOperations; + addLong(this.rxBytes, i, fracRxBytes); rxBytes -= fracRxBytes; + addLong(this.rxPackets, i, fracRxPackets); rxPackets -= fracRxPackets; + addLong(this.txBytes, i, fracTxBytes); txBytes -= fracTxBytes; + addLong(this.txPackets, i, fracTxPackets); txPackets -= fracTxPackets; + addLong(this.operations, i, fracOperations); operations -= fracOperations; duration -= overlap; } + + totalBytes += entry.rxBytes + entry.txBytes; } /** @@ -394,6 +419,7 @@ public class NetworkStatsHistory implements Parcelable { /** * Remove buckets older than requested cutoff. */ + @Deprecated public void removeBucketsBefore(long cutoff) { int i; for (i = 0; i < bucketCount; i++) { @@ -415,6 +441,8 @@ public class NetworkStatsHistory implements Parcelable { if (txPackets != null) txPackets = Arrays.copyOfRange(txPackets, i, length); if (operations != null) operations = Arrays.copyOfRange(operations, i, length); bucketCount -= i; + + // TODO: subtract removed values from totalBytes } } @@ -527,19 +555,17 @@ public class NetworkStatsHistory implements Parcelable { return (long) (start + (r.nextFloat() * (end - start))); } - public void dump(String prefix, PrintWriter pw, boolean fullHistory) { - pw.print(prefix); + public void dump(IndentingPrintWriter pw, boolean fullHistory) { pw.print("NetworkStatsHistory: bucketDuration="); pw.println(bucketDuration); + pw.increaseIndent(); final int start = fullHistory ? 0 : Math.max(0, bucketCount - 32); if (start > 0) { - pw.print(prefix); - pw.print(" (omitting "); pw.print(start); pw.println(" buckets)"); + pw.print("(omitting "); pw.print(start); pw.println(" buckets)"); } for (int i = start; i < bucketCount; i++) { - pw.print(prefix); - pw.print(" bucketStart="); pw.print(bucketStart[i]); + pw.print("bucketStart="); pw.print(bucketStart[i]); if (activeTime != null) { pw.print(" activeTime="); pw.print(activeTime[i]); } if (rxBytes != null) { pw.print(" rxBytes="); pw.print(rxBytes[i]); } if (rxPackets != null) { pw.print(" rxPackets="); pw.print(rxPackets[i]); } @@ -548,12 +574,14 @@ public class NetworkStatsHistory implements Parcelable { if (operations != null) { pw.print(" operations="); pw.print(operations[i]); } pw.println(); } + + pw.decreaseIndent(); } @Override public String toString() { final CharArrayWriter writer = new CharArrayWriter(); - dump("", new PrintWriter(writer), false); + dump(new IndentingPrintWriter(writer, " "), false); return writer.toString(); } @@ -579,6 +607,10 @@ public class NetworkStatsHistory implements Parcelable { if (array != null) array[i] += value; } + public int estimateResizeBuckets(long newBucketDuration) { + return (int) (size() * getBucketDuration() / newBucketDuration); + } + /** * Utility methods for interacting with {@link DataInputStream} and * {@link DataOutputStream}, mostly dealing with writing partial arrays. diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index 418b82f..8ebfd8d 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -18,6 +18,7 @@ package android.net; import static android.net.ConnectivityManager.TYPE_ETHERNET; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivityManager.TYPE_WIFI_P2P; import static android.net.ConnectivityManager.TYPE_WIMAX; import static android.net.NetworkIdentity.scrubSubscriberId; import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G; @@ -231,10 +232,13 @@ public class NetworkTemplate implements Parcelable { * Check if matches Wi-Fi network template. */ private boolean matchesWifi(NetworkIdentity ident) { - if (ident.mType == TYPE_WIFI) { - return true; + switch (ident.mType) { + case TYPE_WIFI: + case TYPE_WIFI_P2P: + return true; + default: + return false; } - return false; } /** diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 8bdb669..dfdea38 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -195,7 +195,7 @@ public class TrafficStats { // subtract starting values and return delta final NetworkStats profilingStop = getDataLayerSnapshotForUid(context); final NetworkStats profilingDelta = NetworkStats.subtract( - profilingStop, sActiveProfilingStart, null); + profilingStop, sActiveProfilingStart, null, null); sActiveProfilingStart = null; return profilingDelta; } diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 9a53d76..270e9be 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -45,4 +45,5 @@ interface IPowerManager // sets the brightness of the backlights (screen, keyboard, button) 0-255 void setBacklightBrightness(int brightness); void setAttentionLight(boolean on, int color); + void setAutoBrightnessAdjustment(float adj); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0202c47..ef8cb16 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1401,6 +1401,12 @@ public final class Settings { public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode"; /** + * Adjustment to auto-brightness to make it generally more (>0.0 <1.0) + * or less (<0.0 >-1.0) bright. + */ + public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj"; + + /** * SCREEN_BRIGHTNESS_MODE value for manual mode. */ public static final int SCREEN_BRIGHTNESS_MODE_MANUAL = 0; @@ -1927,6 +1933,7 @@ public final class Settings { SCREEN_OFF_TIMEOUT, SCREEN_BRIGHTNESS, SCREEN_BRIGHTNESS_MODE, + SCREEN_AUTO_BRIGHTNESS_ADJ, VIBRATE_ON, MODE_RINGER, MODE_RINGER_STREAMS_AFFECTED, @@ -4104,17 +4111,38 @@ public final class Settings { /** {@hide} */ public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval"; /** {@hide} */ - public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold"; + public static final String NETSTATS_TIME_CACHE_MAX_AGE = "netstats_time_cache_max_age"; + /** {@hide} */ + public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes"; /** {@hide} */ - public static final String NETSTATS_NETWORK_BUCKET_DURATION = "netstats_network_bucket_duration"; + public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled"; + + /** {@hide} */ + public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration"; /** {@hide} */ - public static final String NETSTATS_NETWORK_MAX_HISTORY = "netstats_network_max_history"; + public static final String NETSTATS_DEV_PERSIST_BYTES = "netstats_dev_persist_bytes"; + /** {@hide} */ + public static final String NETSTATS_DEV_ROTATE_AGE = "netstats_dev_rotate_age"; + /** {@hide} */ + public static final String NETSTATS_DEV_DELETE_AGE = "netstats_dev_delete_age"; + /** {@hide} */ public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration"; /** {@hide} */ - public static final String NETSTATS_UID_MAX_HISTORY = "netstats_uid_max_history"; + public static final String NETSTATS_UID_PERSIST_BYTES = "netstats_uid_persist_bytes"; + /** {@hide} */ + public static final String NETSTATS_UID_ROTATE_AGE = "netstats_uid_rotate_age"; + /** {@hide} */ + public static final String NETSTATS_UID_DELETE_AGE = "netstats_uid_delete_age"; + + /** {@hide} */ + public static final String NETSTATS_UID_TAG_BUCKET_DURATION = "netstats_uid_tag_bucket_duration"; + /** {@hide} */ + public static final String NETSTATS_UID_TAG_PERSIST_BYTES = "netstats_uid_tag_persist_bytes"; + /** {@hide} */ + public static final String NETSTATS_UID_TAG_ROTATE_AGE = "netstats_uid_tag_rotate_age"; /** {@hide} */ - public static final String NETSTATS_TAG_MAX_HISTORY = "netstats_tag_max_history"; + public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age"; /** Preferred NTP server. {@hide} */ public static final String NTP_SERVER = "ntp_server"; diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index c184c11..a52e2ba 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -109,6 +109,9 @@ class MeasuredText { for (int i = 0; i < spans.length; i++) { int startInPara = spanned.getSpanStart(spans[i]) - start; int endInPara = spanned.getSpanEnd(spans[i]) - start; + // The span interval may be larger and must be restricted to [start, end[ + if (startInPara < 0) startInPara = 0; + if (endInPara > len) endInPara = len; for (int j = startInPara; j < endInPara; j++) { mChars[j] = '\uFFFC'; } diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index e06d661..c08a402 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -22,6 +22,7 @@ import android.graphics.ColorFilter; import android.graphics.DrawFilter; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; import android.graphics.Path; import android.graphics.Picture; import android.graphics.PorterDuff; @@ -546,6 +547,7 @@ class GLES20Canvas extends HardwareCanvas { private static native void nSetMatrix(int renderer, int matrix); + @SuppressWarnings("deprecation") @Override public void getMatrix(Matrix matrix) { nGetMatrix(mRenderer, matrix.native_instance); @@ -658,8 +660,17 @@ class GLES20Canvas extends HardwareCanvas { @Override public void setDrawFilter(DrawFilter filter) { mFilter = filter; + if (filter == null) { + nResetPaintFilter(mRenderer); + } else if (filter instanceof PaintFlagsDrawFilter) { + PaintFlagsDrawFilter flagsFilter = (PaintFlagsDrawFilter) filter; + nSetupPaintFilter(mRenderer, flagsFilter.clearBits, flagsFilter.setBits); + } } + private static native void nResetPaintFilter(int renderer); + private static native void nSetupPaintFilter(int renderer, int clearBits, int setBits); + @Override public DrawFilter getDrawFilter() { return mFilter; @@ -968,6 +979,7 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawPoints(int renderer, float[] points, int offset, int count, int paint); + @SuppressWarnings("deprecation") @Override public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) { if (index < 0 || index + count > text.length || count * 2 > pos.length) { @@ -985,6 +997,7 @@ class GLES20Canvas extends HardwareCanvas { private static native void nDrawPosText(int renderer, char[] text, int index, int count, float[] pos, int paint); + @SuppressWarnings("deprecation") @Override public void drawPosText(String text, float[] pos, Paint paint) { if (text.length() * 2 > pos.length) { diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index c53fc6b..7fd3389 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -185,7 +185,8 @@ public final class ViewTreeObserver { mTouchableInsets = TOUCHABLE_INSETS_FRAME; } - @Override public boolean equals(Object o) { + @Override + public boolean equals(Object o) { try { if (o == null) { return false; @@ -357,10 +358,26 @@ public final class ViewTreeObserver { * @param victim The callback to remove * * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @deprecated Use #removeOnGlobalLayoutListener instead * * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) */ + @Deprecated public void removeGlobalOnLayoutListener(OnGlobalLayoutListener victim) { + removeOnGlobalLayoutListener(victim); + } + + /** + * Remove a previously installed global layout callback + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnGlobalLayoutListener(OnGlobalLayoutListener) + */ + public void removeOnGlobalLayoutListener(OnGlobalLayoutListener victim) { checkIsAlive(); if (mOnGlobalLayoutListeners == null) { return; diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 5ec1ec3..bd02d62 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -193,10 +193,12 @@ public class BaseInputConnection implements InputConnection { /** * The default implementation performs the deletion around the current * selection position of the editable text. + * @param beforeLength + * @param afterLength */ - public boolean deleteSurroundingText(int leftLength, int rightLength) { - if (DEBUG) Log.v(TAG, "deleteSurroundingText " + leftLength - + " / " + rightLength); + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength + + " / " + afterLength); final Editable content = getEditable(); if (content == null) return false; @@ -226,17 +228,17 @@ public class BaseInputConnection implements InputConnection { int deleted = 0; - if (leftLength > 0) { - int start = a - leftLength; + if (beforeLength > 0) { + int start = a - beforeLength; if (start < 0) start = 0; content.delete(start, a); deleted = a - start; } - if (rightLength > 0) { + if (afterLength > 0) { b = b - deleted; - int end = b + rightLength; + int end = b + afterLength; if (end > content.length()) end = content.length(); content.delete(b, end); diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index a6639d1..bc2a270 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -138,19 +138,20 @@ public interface InputConnection { int flags); /** - * Delete <var>leftLength</var> characters of text before the current cursor - * position, and delete <var>rightLength</var> characters of text after the + * 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. * - * @param leftLength The number of characters to be deleted before the + * + * @param beforeLength The number of characters to be deleted before the * current cursor position. - * @param rightLength The number of characters to be deleted after the + * @param afterLength The number of characters to be deleted after the * current cursor position. - * + * * @return Returns true on success, false if the input connection is no longer * valid. */ - public boolean deleteSurroundingText(int leftLength, int rightLength); + public boolean deleteSurroundingText(int beforeLength, int afterLength); /** * Set composing text around the current cursor position with the given text, diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 690ea85..a48473e 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -62,8 +62,8 @@ public class InputConnectionWrapper implements InputConnection { return mTarget.getExtractedText(request, flags); } - public boolean deleteSurroundingText(int leftLength, int rightLength) { - return mTarget.deleteSurroundingText(leftLength, rightLength); + public boolean deleteSurroundingText(int beforeLength, int afterLength) { + return mTarget.deleteSurroundingText(beforeLength, afterLength); } public boolean setComposingText(CharSequence text, int newCursorPosition) { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 148be5c..3697635 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -384,17 +384,17 @@ public class WebView extends AbsoluteLayout } @Override - public boolean deleteSurroundingText(int leftLength, int rightLength) { + public boolean deleteSurroundingText(int beforeLength, int afterLength) { // Look for one-character delete and send it as a key press. - if (leftLength == 1 && rightLength == 0) { + if (beforeLength == 1 && afterLength == 0) { sendKeyPress(KeyEvent.KEYCODE_DEL); - } else if (leftLength == 0 && rightLength == 1){ + } else if (beforeLength == 0 && afterLength == 1){ sendKeyPress(KeyEvent.KEYCODE_FORWARD_DEL); } else if (mWebViewCore != null) { mWebViewCore.sendMessage(EventHub.DELETE_SURROUNDING_TEXT, - leftLength, rightLength); + beforeLength, afterLength); } - return super.deleteSurroundingText(leftLength, rightLength); + return super.deleteSurroundingText(beforeLength, afterLength); } } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index baeb0ed..fe5c04c 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -1742,6 +1742,7 @@ public final class WebViewCore { Rect rect = (Rect) msg.obj; nativeScrollLayer(mNativeClass, nativeLayer, rect); + break; case DELETE_TEXT: { int[] handles = (int[]) msg.obj; diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java index 60b24bc..be6b4e2 100644 --- a/core/java/android/widget/ActivityChooserView.java +++ b/core/java/android/widget/ActivityChooserView.java @@ -33,8 +33,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.widget.ActivityChooserModel.ActivityChooserModelClient; /** @@ -366,7 +364,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod getListPopupWindow().dismiss(); ViewTreeObserver viewTreeObserver = getViewTreeObserver(); if (viewTreeObserver.isAlive()) { - viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener); + viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener); } } return true; @@ -400,7 +398,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod } ViewTreeObserver viewTreeObserver = getViewTreeObserver(); if (viewTreeObserver.isAlive()) { - viewTreeObserver.removeGlobalOnLayoutListener(mOnGlobalLayoutListener); + viewTreeObserver.removeOnGlobalLayoutListener(mOnGlobalLayoutListener); } mIsAttachedToWindow = false; } @@ -547,6 +545,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod position = mAdapter.getShowDefaultActivity() ? position : position + 1; Intent launchIntent = mAdapter.getDataModel().chooseActivity(position); if (launchIntent != null) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); mContext.startActivity(launchIntent); } } @@ -564,6 +563,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod final int index = mAdapter.getDataModel().getActivityIndex(defaultActivity); Intent launchIntent = mAdapter.getDataModel().chooseActivity(index); if (launchIntent != null) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); mContext.startActivity(launchIntent); } } else if (view == mExpandActivityOverflowButton) { diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index e226d37..bb00049 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -558,7 +558,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> mCurrentWindowEnd = newWindowEnd; mCurrentWindowStartUnbounded = newWindowStartUnbounded; if (mRemoteViewsAdapter != null) { - mRemoteViewsAdapter.setVisibleRangeHint(mCurrentWindowStart, mCurrentWindowEnd); + int adapterStart = modulo(mCurrentWindowStart, adapterCount); + int adapterEnd = modulo(mCurrentWindowEnd, adapterCount); + mRemoteViewsAdapter.setVisibleRangeHint(adapterStart, adapterEnd); } } requestLayout(); diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index a210f0b..182df7a 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -71,6 +71,11 @@ import com.android.internal.R; public class NumberPicker extends LinearLayout { /** + * The number of items show in the selector wheel. + */ + public static final int SELECTOR_WHEEL_ITEM_COUNT = 5; + + /** * The default update interval during long press. */ private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300; @@ -1137,14 +1142,17 @@ public class NumberPicker extends LinearLayout { * items shown on the selector wheel) the selector wheel wrapping is * enabled. * </p> - * + * <p> + * <strong>Note:</strong> If the number of items, i.e. the range + * ({@link #getMaxValue()} - {@link #getMinValue()}) is less than + * {@link #SELECTOR_WHEEL_ITEM_COUNT}, the selector wheel will not + * wrap. Hence, in such a case calling this method is a NOP. + * </p> * @param wrapSelectorWheel Whether to wrap. */ public void setWrapSelectorWheel(boolean wrapSelectorWheel) { - if (wrapSelectorWheel && (mMaxValue - mMinValue) < mSelectorIndices.length) { - throw new IllegalStateException("Range less than selector items count."); - } - if (wrapSelectorWheel != mWrapSelectorWheel) { + final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length; + if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) { mWrapSelectorWheel = wrapSelectorWheel; updateIncrementAndDecrementButtonsVisibilityState(); } diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java index bb27b73..22e9ef1 100644 --- a/core/java/android/widget/ShareActionProvider.java +++ b/core/java/android/widget/ShareActionProvider.java @@ -279,6 +279,7 @@ public class ShareActionProvider extends ActionProvider { final int itemId = item.getItemId(); Intent launchIntent = dataModel.chooseActivity(itemId); if (launchIntent != null) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); mContext.startActivity(launchIntent); } return true; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f78c247..9fb26ae 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -342,6 +342,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mTextEditSuggestionItemLayout; private SuggestionsPopupWindow mSuggestionsPopupWindow; private SuggestionRangeSpan mSuggestionRangeSpan; + private Runnable mShowSuggestionRunnable; private int mCursorDrawableRes; private final Drawable[] mCursorDrawable = new Drawable[2]; @@ -4513,6 +4514,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mSelectionModifierCursorController.onDetached(); } + if (mShowSuggestionRunnable != null) { + removeCallbacks(mShowSuggestionRunnable); + } + hideControllers(); resetResolvedDrawables(); @@ -8330,6 +8335,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getSelectionController().onTouchEvent(event); } + if (mShowSuggestionRunnable != null) { + removeCallbacks(mShowSuggestionRunnable); + } + if (action == MotionEvent.ACTION_DOWN) { mLastDownPositionX = event.getX(); mLastDownPositionY = event.getY(); @@ -8370,17 +8379,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(), getSelectionEnd(), ClickableSpan.class); - if (links.length != 0) { + if (links.length > 0) { links[0].onClick(this); handled = true; } } 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 +8402,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (!extractedTextModeWillBeStarted()) { if (isCursorInsideEasyCorrectionSpan()) { - showSuggestions(); + if (mShowSuggestionRunnable == null) { + mShowSuggestionRunnable = new Runnable() { + public void run() { + showSuggestions(); + } + }; + } + postDelayed(mShowSuggestionRunnable, + ViewConfiguration.getDoubleTapTimeout()); } else if (hasInsertionController()) { + // Move cursor + final int offset = getOffsetForPosition(event.getX(), event.getY()); + Selection.setSelection((Spannable) mText, offset); getInsertionController().show(); } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 9c45dc6..6a99a2b 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -243,7 +243,7 @@ public class ZygoteInit { private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime(); - InputStream is = ZygoteInit.class.getClassLoader().getResourceAsStream( + InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream( PRELOADED_CLASSES); if (is == null) { Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + "."); diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index edeb2a8..d1aa1ce 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -142,6 +142,14 @@ public class ArrayUtils return false; } + public static long total(long[] array) { + long total = 0; + for (long value : array) { + total += value; + } + return total; + } + /** * Appends an element to a copy of the array and returns the copy. * @param array The original array, or null to represent an empty array. diff --git a/core/java/com/android/internal/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java index 3ce95e7..8a8f315 100644 --- a/core/java/com/android/internal/util/FileRotator.java +++ b/core/java/com/android/internal/util/FileRotator.java @@ -17,9 +17,9 @@ package com.android.internal.util; import android.os.FileUtils; +import android.util.Slog; -import com.android.internal.util.FileRotator.Reader; -import com.android.internal.util.FileRotator.Writer; +import com.android.internal.util.FileRotator.Rewriter; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -41,12 +41,15 @@ import libcore.io.IoUtils; * Instead of manipulating files directly, users implement interfaces that * perform operations on {@link InputStream} and {@link OutputStream}. This * enables atomic rewriting of file contents in - * {@link #combineActive(Reader, Writer, long)}. + * {@link #rewriteActive(Rewriter, long)}. * <p> * Users must periodically call {@link #maybeRotate(long)} to perform actual * rotation. Not inherently thread safe. */ public class FileRotator { + private static final String TAG = "FileRotator"; + private static final boolean LOGD = true; + private final File mBasePath; private final String mPrefix; private final long mRotateAgeMillis; @@ -73,6 +76,15 @@ public class FileRotator { } /** + * External class that reads existing data from given {@link InputStream}, + * then writes any modified data to {@link OutputStream}. + */ + public interface Rewriter extends Reader, Writer { + public void reset(); + public boolean shouldWrite(); + } + + /** * Create a file rotator. * * @param basePath Directory under which all files will be placed. @@ -96,6 +108,8 @@ public class FileRotator { if (!name.startsWith(mPrefix)) continue; if (name.endsWith(SUFFIX_BACKUP)) { + if (LOGD) Slog.d(TAG, "recovering " + name); + final File backupFile = new File(mBasePath, name); final File file = new File( mBasePath, name.substring(0, name.length() - SUFFIX_BACKUP.length())); @@ -104,6 +118,8 @@ public class FileRotator { backupFile.renameTo(file); } else if (name.endsWith(SUFFIX_NO_BACKUP)) { + if (LOGD) Slog.d(TAG, "recovering " + name); + final File noBackupFile = new File(mBasePath, name); final File file = new File( mBasePath, name.substring(0, name.length() - SUFFIX_NO_BACKUP.length())); @@ -116,26 +132,95 @@ public class FileRotator { } /** - * Atomically combine data with existing data in currently active file. - * Maintains a backup during write, which is restored if the write fails. + * Delete all files managed by this rotator. */ - public void combineActive(Reader reader, Writer writer, long currentTimeMillis) + public void deleteAll() { + final FileInfo info = new FileInfo(mPrefix); + for (String name : mBasePath.list()) { + if (!info.parse(name)) continue; + + // delete each file that matches parser + new File(mBasePath, name).delete(); + } + } + + /** + * Process currently active file, first reading any existing data, then + * writing modified data. Maintains a backup during write, which is restored + * if the write fails. + */ + public void rewriteActive(Rewriter rewriter, long currentTimeMillis) throws IOException { final String activeName = getActiveName(currentTimeMillis); + rewriteSingle(rewriter, activeName); + } - final File file = new File(mBasePath, activeName); + @Deprecated + public void combineActive(final Reader reader, final Writer writer, long currentTimeMillis) + throws IOException { + rewriteActive(new Rewriter() { + /** {@inheritDoc} */ + public void reset() { + // ignored + } + + /** {@inheritDoc} */ + public void read(InputStream in) throws IOException { + reader.read(in); + } + + /** {@inheritDoc} */ + public boolean shouldWrite() { + return true; + } + + /** {@inheritDoc} */ + public void write(OutputStream out) throws IOException { + writer.write(out); + } + }, currentTimeMillis); + } + + /** + * Process all files managed by this rotator, usually to rewrite historical + * data. Each file is processed atomically. + */ + public void rewriteAll(Rewriter rewriter) throws IOException { + final FileInfo info = new FileInfo(mPrefix); + for (String name : mBasePath.list()) { + if (!info.parse(name)) continue; + + // process each file that matches parser + rewriteSingle(rewriter, name); + } + } + + /** + * Process a single file atomically, first reading any existing data, then + * writing modified data. Maintains a backup during write, which is restored + * if the write fails. + */ + private void rewriteSingle(Rewriter rewriter, String name) throws IOException { + if (LOGD) Slog.d(TAG, "rewriting " + name); + + final File file = new File(mBasePath, name); final File backupFile; + rewriter.reset(); + if (file.exists()) { // read existing data - readFile(file, reader); + readFile(file, rewriter); + + // skip when rewriter has nothing to write + if (!rewriter.shouldWrite()) return; // backup existing data during write - backupFile = new File(mBasePath, activeName + SUFFIX_BACKUP); + backupFile = new File(mBasePath, name + SUFFIX_BACKUP); file.renameTo(backupFile); try { - writeFile(file, writer); + writeFile(file, rewriter); // write success, delete backup backupFile.delete(); @@ -148,11 +233,11 @@ public class FileRotator { } else { // create empty backup during write - backupFile = new File(mBasePath, activeName + SUFFIX_NO_BACKUP); + backupFile = new File(mBasePath, name + SUFFIX_NO_BACKUP); backupFile.createNewFile(); try { - writeFile(file, writer); + writeFile(file, rewriter); // write success, delete empty backup backupFile.delete(); @@ -176,6 +261,8 @@ public class FileRotator { // read file when it overlaps if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) { + if (LOGD) Slog.d(TAG, "reading matching " + name); + final File file = new File(mBasePath, name); readFile(file, reader); } @@ -224,16 +311,20 @@ public class FileRotator { if (!info.parse(name)) continue; if (info.isActive()) { - // found active file; rotate if old enough - if (info.startMillis < rotateBefore) { + if (info.startMillis <= rotateBefore) { + // found active file; rotate if old enough + if (LOGD) Slog.d(TAG, "rotating " + name); + info.endMillis = currentTimeMillis; final File file = new File(mBasePath, name); final File destFile = new File(mBasePath, info.build()); file.renameTo(destFile); } - } else if (info.endMillis < deleteBefore) { + } else if (info.endMillis <= deleteBefore) { // found rotated file; delete if old enough + if (LOGD) Slog.d(TAG, "deleting " + name); + final File file = new File(mBasePath, name); file.delete(); } diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java new file mode 100644 index 0000000..3dd2284 --- /dev/null +++ b/core/java/com/android/internal/util/IndentingPrintWriter.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import java.io.PrintWriter; +import java.io.Writer; + +/** + * Lightweight wrapper around {@link PrintWriter} that automatically indents + * newlines based on internal state. Delays writing indent until first actual + * write on a newline, enabling indent modification after newline. + */ +public class IndentingPrintWriter extends PrintWriter { + private final String mIndent; + + private StringBuilder mBuilder = new StringBuilder(); + private String mCurrent = new String(); + private boolean mEmptyLine = true; + + public IndentingPrintWriter(Writer writer, String indent) { + super(writer); + mIndent = indent; + } + + public void increaseIndent() { + mBuilder.append(mIndent); + mCurrent = mBuilder.toString(); + } + + public void decreaseIndent() { + mBuilder.delete(0, mIndent.length()); + mCurrent = mBuilder.toString(); + } + + @Override + public void println() { + super.println(); + mEmptyLine = true; + } + + @Override + public void write(char[] buf, int offset, int count) { + if (mEmptyLine) { + mEmptyLine = false; + super.print(mCurrent); + } + super.write(buf, offset, count); + } +} diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index a235d9a..9024d8d 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -387,9 +387,9 @@ public class InputConnectionWrapper implements InputConnection { } } - public boolean deleteSurroundingText(int leftLength, int rightLength) { + public boolean deleteSurroundingText(int beforeLength, int afterLength) { try { - mIInputContext.deleteSurroundingText(leftLength, rightLength); + mIInputContext.deleteSurroundingText(beforeLength, afterLength); return true; } catch (RemoteException e) { return false; diff --git a/core/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 @@ public: 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 9f3238a..9bcfa5f 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -466,7 +466,8 @@ public: 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 @@ public: } } - 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 @@ public: 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..71c283a 100644 --- a/core/jni/android/graphics/TextLayoutCache.cpp +++ b/core/jni/android/graphics/TextLayoutCache.cpp @@ -93,7 +93,7 @@ void TextLayoutCache::clear() { /* * 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 @@ TextLayoutEngine::~TextLayoutEngine() { // 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 @@ void TextLayoutEngine::computeValues(TextLayoutCacheValue* value, SkPaint* paint #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 @@ static void logGlyphs(HB_ShaperItem shaperItem) { } } -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 @@ void TextLayoutEngine::computeRunValues(SkPaint* paint, const UChar* chars, } -size_t TextLayoutEngine::shapeFontRun(SkPaint* paint, bool isRTL) { +size_t TextLayoutEngine::shapeFontRun(const SkPaint* paint, bool isRTL) { // Reset kerning mShaperItem.kerning_applied = false; 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 @@ public: */ 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 @@ public: 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 @@ private: */ 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 @@ static void android_view_GLES20Canvas_setupShadow(JNIEnv* env, jobject clazz, } // ---------------------------------------------------------------------------- +// 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 @@ static JNINativeMethod gMethods[] = { { "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/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" /> - <rotate android:fromDegrees="0" android:toDegrees="90" + <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: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" /> - <rotate android:fromDegrees="0" android:toDegrees="-90" + <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: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..e3f48e4 --- /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..e3f48e4 --- /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/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 @@ above.</p> <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/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 @@ changed.</li> <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 @@ changed.</li> <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 @@ changed.</li> </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/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 @@ package android.graphics; * 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 @@ public class PaintFlagsDrawFilter extends DrawFilter { * @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..6075ec3 100644 --- a/graphics/java/android/graphics/PixelXorXfermode.java +++ b/graphics/java/android/graphics/PixelXorXfermode.java @@ -22,6 +22,7 @@ package android.graphics; * 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/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 @@ const char* DisplayList::OP_NAMES[] = { "SetupColorFilter", "ResetShadow", "SetupShadow", + "ResetPaintFilter", + "SetupPaintFilter", "DrawGLFunction" }; @@ -249,7 +251,7 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ bool DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, uint32_t level) 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 @@ void DisplayListRenderer::setupShadow(float radius, float dx, float dy, int colo 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 @@ public: SetupColorFilter, ResetShadow, SetupShadow, + ResetPaintFilter, + SetupPaintFilter, DrawGLFunction, }; @@ -177,8 +179,8 @@ private: return (SkPath*) getInt(); } - SkPaint* getPaint() { - return (SkPaint*) getInt(); + SkPaint* getPaint(OpenGLRenderer& renderer) { + return renderer.filterPaint((SkPaint*) getInt()); } DisplayList* getDisplayList() { @@ -304,6 +306,9 @@ public: 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 @@ OpenGLRenderer::OpenGLRenderer(): mCaches(Caches::getInstance()) { mShader = NULL; mColorFilter = NULL; mHasShadow = false; + mHasDrawFilter = false; memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices)); @@ -2399,6 +2400,31 @@ void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) { } /////////////////////////////////////////////////////////////////////////////// +// 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 @@ public: 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 @@ private: float mShadowDy; int mShadowColor; + // Draw filters + bool mHasDrawFilter; + int mPaintFilterClearBits; + int mPaintFilterSetBits; + SkPaint mFilteredPaint; + // Various caches Caches& mCaches; 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 @@ void AdjThrInit(ADJ_THR_STATE *hAdjThr, /* 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/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.app.StatusBarManager; 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 @@ public class NavigationBarView extends LinearLayout { 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 @@ public class NavigationBarView extends LinearLayout { } } + @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 @@ public class NavigationBarView extends LinearLayout { } } + 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/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.CompoundButton; 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 @@ public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedC 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(); - - mButton = mDialog.getButton(DialogInterface.BUTTON_POSITIVE); + 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(); + + getWindow().setCloseOnTouchOutside(false); + mButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); mButton.setEnabled(false); } catch (Exception e) { Log.e(TAG, "onResume", e); @@ -87,12 +85,7 @@ public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedC } @Override - protected void onPause() { - super.onPause(); - if (mDialog != null) { - mDialog.setOnDismissListener(null); - mDialog.dismiss(); - } + public void onBackPressed() { } @Override @@ -103,16 +96,11 @@ public class ConfirmDialog extends Activity implements CompoundButton.OnCheckedC @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.CompoundButton; 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 @@ public class ManageDialog extends Activity implements Handler.Callback, 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 @@ public class ManageDialog extends Activity implements Handler.Callback, @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 @@ public class ManageDialog extends Activity implements Handler.Callback, } @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/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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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/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 @@ option java_package com.android.server # --------------------------- # 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.Process; 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 android.view.WindowManagerPolicy; 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 @@ public class PowerManagerService extends IPowerManager.Stub // 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 @@ public class PowerManagerService extends IPowerManager.Stub // 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 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class PowerManagerService extends IPowerManager.Stub // 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 @@ public class PowerManagerService extends IPowerManager.Stub // 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 @@ public class PowerManagerService extends IPowerManager.Stub + 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 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class PowerManagerService extends IPowerManager.Stub } 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 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class PowerManagerService extends IPowerManager.Stub int value = (int)mLightSensorPendingValue; mLightSensorPendingDecrease = false; mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value); + lightSensorChangedLocked(value, false); } } } @@ -2485,12 +2529,12 @@ public class PowerManagerService extends IPowerManager.Stub // 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 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class PowerManagerService extends IPowerManager.Stub } }; - 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 @@ public class PowerManagerService extends IPowerManager.Stub mHandler.removeCallbacks(mAutoBrightnessTask); mLightSensorPendingDecrease = false; mLightSensorPendingIncrease = false; - lightSensorChangedLocked(value); + lightSensorChangedLocked(value, immediate); } else { if ((value > mLightSensorValue && mLightSensorPendingDecrease) || (value < mLightSensorValue && mLightSensorPendingIncrease) || @@ -3160,7 +3223,7 @@ public class PowerManagerService extends IPowerManager.Stub 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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.TAG_NONE; 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.ContentResolver; 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; 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.Slog; 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 @@ import libcore.io.IoUtils; */ 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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(); - - /** 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 NetworkStats mLastPollNetworkDevSnapshot; - private NetworkStats mLastPollNetworkXtSnapshot; - private NetworkStats mLastPollUidSnapshot; - private NetworkStats mLastPollOperationsSnapshot; - - private NetworkStats mLastPersistNetworkDevSnapshot; - private NetworkStats mLastPersistNetworkXtSnapshot; - private NetworkStats mLastPersistUidSnapshot; + /** Current default active iface. */ + private String mActiveIface; + + private final DropBoxNonMonotonicObserver mNonMonotonicObserver = + new DropBoxNonMonotonicObserver(); + + private NetworkStatsRecorder mDevRecorder; + private NetworkStatsRecorder mUidRecorder; + private NetworkStatsRecorder mUidTagRecorder; + + /** 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return; } + // 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); + 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. we delay loading detailed UID stats - // until actually needed. - readNetworkDevStatsLocked(); - readNetworkXtStatsLocked(); - mNetworkStatsLoaded = true; - } + // might need them right away. + mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked(); - // bootstrap initial stats to prevent double-counting later - bootstrapStats(); + // 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { mTeleManager.listen(mPhoneListener, LISTEN_NONE); - if (mNetworkStatsLoaded) { - writeNetworkDevStatsLocked(); - writeNetworkXtStatsLocked(); - } - if (mUidStatsLoaded) { - writeUidStatsLocked(); + 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); } - mNetworkDevStats.clear(); - mNetworkXtStats.clear(); - mUidStats.clear(); - mNetworkStatsLoaded = false; - mUidStatsLoaded = false; } /** @@ -372,7 +394,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { @Override public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) { - mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG); - return getHistoryForNetworkDev(template, 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); + return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields); } - 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; - } - } - - @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()); - } + // 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); } - 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } // splice in operation counts - dataLayer.spliceOperationsFrom(mOperations); + dataLayer.spliceOperationsFrom(mUidOperations); return dataLayer; } @@ -586,8 +486,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // 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(); - - // record uid stats, folding in tethering stats - uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); - uidSnapshot.combineAllValues(tetherSnapshot); - performUidPollLocked(uidSnapshot, currentTime); + // snapshot and record current counters; read UID stats first to + // avoid overcounting dev stats. + final NetworkStats uidSnapshot = getNetworkStatsUidDetail(); + final NetworkStats devSnapshot = getNetworkStatsSummary(); - // 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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"); - synchronized (mStatsLock) { - // TODO: remove this testing code, since it corrupts stats - if (argSet.contains("generate")) { - generateRandomLocked(args); - pw.println("Generated stub stats"); - return; - } + final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - if (argSet.contains("poll")) { + synchronized (mStatsLock) { + 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.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("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); + pw.decreaseIndent(); + + 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(); } - 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(); } } } - /** - * @deprecated only for temporary testing - */ - @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); - } - } - - 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); - } - } - - 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()); - } + private NetworkStats getNetworkStatsSummary() throws RemoteException { + return mNetworkManager.getNetworkStatsSummary(); } /** - * Return the delta between two {@link NetworkStats} snapshots, where {@code - * before} can be {@code null}. + * Return snapshot of current UID statistics, including any + * {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values. */ - 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); - } + private NetworkStats getNetworkStatsUidDetail() throws RemoteException { + final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL); + + // fold tethering stats and operations into uid snapshot + final NetworkStats tetherSnapshot = getNetworkStatsTethering(); + uidSnapshot.combineAllValues(tetherSnapshot); + uidSnapshot.combineAllValues(mUidOperations); + + return uidSnapshot; } /** @@ -1608,35 +932,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - /** - * 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); - } + private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> { + /** {@inheritDoc} */ + public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right, + int rightIndex, String cookie) { + Log.w(TAG, "found non-monotonic values; saving to dropbox"); - @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; - } + // 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'); - /** {@inheritDoc} */ - public int compareTo(UidStatsKey another) { - return Integer.compare(uid, another.uid); + final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService( + Context.DROPBOX_SERVICE); + dropBox.addText(TAG_NETSTATS_ERROR, builder.toString()); } } @@ -1731,26 +1008,35 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 getTimeCacheMaxAge() { + return getSecureLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS); } - public long getNetworkMaxHistory() { - return getSecureLong(NETSTATS_NETWORK_MAX_HISTORY, 90 * DAY_IN_MILLIS); + public long getGlobalAlertBytes() { + return getSecureLong(NETSTATS_GLOBAL_ALERT_BYTES, 2 * MB_IN_BYTES); } - public long getUidBucketDuration() { - return getSecureLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS); + public boolean getSampleEnabled() { + return getSecureBoolean(NETSTATS_SAMPLE_ENABLED, true); } - public long getUidMaxHistory() { - return getSecureLong(NETSTATS_UID_MAX_HISTORY, 90 * DAY_IN_MILLIS); + + 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 long getTagMaxHistory() { - return getSecureLong(NETSTATS_TAG_MAX_HISTORY, 30 * 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 long getTimeCacheMaxAge() { - return 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/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 @@ import android.view.animation.Transformation; 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 @@ class ScreenRotationAnimation { 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 @@ class ScreenRotationAnimation { 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 @@ class ScreenRotationAnimation { } // 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 @@ class ScreenRotationAnimation { 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 @@ class ScreenRotationAnimation { // 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; + + 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 (mBlackFrame == null) { + if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( + WindowManagerService.TAG, + ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation"); + Surface.openTransaction(); - mExitAnimation.restrictDuration(maxAnimationDuration); - mExitAnimation.scaleCurrentDuration(animationScale); - mEnterAnimation.restrictDuration(maxAnimationDuration); - mEnterAnimation.scaleCurrentDuration(animationScale); + 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"); + } + } - if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG, - ">>> OPEN TRANSACTION ScreenRotationAnimation.dismiss"); - Surface.openTransaction(); + return true; + } - 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"); + /** + * 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 @@ class ScreenRotationAnimation { } if (mBlackFrame != null) { mBlackFrame.kill(); + mBlackFrame = null; + } + if (mStartExitAnimation != null) { + mStartExitAnimation.cancel(); + mStartExitAnimation = null; + } + if (mStartEnterAnimation != null) { + mStartEnterAnimation.cancel(); + mStartEnterAnimation = null; } - if (mExitAnimation != null) { - mExitAnimation.cancel(); - mExitAnimation = null; + if (mFinishExitAnimation != null) { + mFinishExitAnimation.cancel(); + mFinishExitAnimation = null; } - if (mEnterAnimation != null) { - mEnterAnimation.cancel(); - mEnterAnimation = 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; - } - - 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 (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; } - 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()); - } + 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; + } + } + + 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 a702ce8..19d94a1 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -733,7 +733,7 @@ public class WindowManagerService extends IWindowManager.Stub //Looper.myLooper().setMessageLogging(new LogPrinter( // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_DISPLAY); + android.os.Process.THREAD_PRIORITY_FOREGROUND); android.os.Process.setCanSelfBackground(false); mPolicy.init(mContext, mService, mService, mPM); @@ -2543,8 +2543,12 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } 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 @@ public class WindowManagerService extends IWindowManager.Stub // 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub // 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 @@ public class WindowManagerService extends IWindowManager.Stub Configuration computeNewConfigurationLocked() { Configuration config = new Configuration(); config.fontScale = 0; - if (!computeNewConfigurationLocked(config)) { + if (!computeScreenConfigurationLocked(config)) { return null; } return config; @@ -6011,12 +6030,10 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub // 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 @@ public class WindowManagerService extends IWindowManager.Stub // 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 9118381..1067cad 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -238,6 +238,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { // 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; @@ -1449,7 +1455,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mViewVisibility == View.GONE || !mRelayoutCalled || (atoken == null && mRootToken.hidden) - || (atoken != null && atoken.hiddenRequested) + || (atoken != null && (atoken.hiddenRequested || atoken.hidden)) || mAttachedHidden || mExiting || mDestroying; } @@ -1728,8 +1734,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { 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/tests/servicestests/res/raw/netstats_uid_v4 b/services/tests/servicestests/res/raw/netstats_uid_v4 Binary files differnew file mode 100644 index 0000000..e75fc1c --- /dev/null +++ b/services/tests/servicestests/res/raw/netstats_uid_v4 diff --git a/services/tests/servicestests/res/raw/netstats_v1 b/services/tests/servicestests/res/raw/netstats_v1 Binary files differnew file mode 100644 index 0000000..e80860a --- /dev/null +++ b/services/tests/servicestests/res/raw/netstats_v1 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.MINUTE_IN_MILLIS; 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.os.INetworkManagementService; 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { } + // TODO: simulate reboot to test bucket resize + @Suppress public void testStatsBucketResize() throws Exception { NetworkStatsHistory history = null; @@ -602,7 +604,6 @@ public class NetworkStatsServiceTest extends AndroidTestCase { assertUidTotal(sTemplateImsi1, UID_RED, 1536L, 12L, 1280L, 10L, 10); verifyAndReset(); - } public void testSummaryForAllUid() throws Exception { @@ -755,11 +756,15 @@ public class NetworkStatsServiceTest extends AndroidTestCase { 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { } 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 @@ public class NetworkStatsServiceTest extends AndroidTestCase { } 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/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 @@ public class CatService extends Handler implements AppInterface { } 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 @@ class ComprehensionTlv { 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 java.util.List; 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.LinearLayout; 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 @@ public class ActivityTestMain extends Activity { } @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/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.Bundle; 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 @@ public class PerformanceTest extends 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 @@ public class PerformanceTest extends 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 @@ public class PerformanceTest extends 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 @@ public class PerformanceTest extends 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 @@ public class PerformanceTest extends 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 @@ public class PerformanceTest extends 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 @@ public class PlaybackGraphs { 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 @@ public class PlaybackGraphs { @Override public double getMax() { - return 1; + return 100; } @Override @@ -108,6 +108,9 @@ public class PlaybackGraphs { } 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 @@ public class PlaybackGraphs { + 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 class PlaybackGraphs { 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 @@ public class PlaybackGraphs { } 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 @@ public class PlaybackGraphs { 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.content.Context; 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 @@ import java.io.ObjectOutputStream; */ 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 @@ public class ProfileActivity extends Activity { LoggingWebViewClient mLoggingWebViewClient = new LoggingWebViewClient(); AutoLoggingWebViewClient mAutoLoggingWebViewClient = new AutoLoggingWebViewClient(); + TimedLoggingWebViewClient mTimedLoggingWebViewClient = new TimedLoggingWebViewClient(); private enum TestingState { NOT_TESTING, @@ -93,18 +97,18 @@ public class ProfileActivity extends Activity { 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,16 +128,46 @@ public class ProfileActivity extends Activity { super.onPageStarted(view, url, favicon); mUrl.setText(url); } + + @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); + } @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + setTestingState(TestingState.PRE_TESTING); + } + } + + private class TimedLoggingWebViewClient extends LoggingWebViewClient { + @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); - view.requestFocus(); + startViewProfiling(false); - startViewProfiling(true); + // 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 @@ -178,11 +212,13 @@ public class ProfileActivity extends Activity { 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 @@ public class ProfileActivity extends Activity { /** 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 class ProfileActivity extends Activity { 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 @@ public class ProfileActivity extends Activity { // 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 @@ public class ProfileActivity extends Activity { }); // 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.content.Context; 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 @@ public class ProfiledWebView extends WebView { 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 @@ public class ProfiledWebView extends WebView { * 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 @@ public class ProfiledWebView extends WebView { @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 @@ public class ProfiledWebView extends WebView { */ @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 @@ public class ProfiledWebView extends WebView { // 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 @@ public class ProfiledWebView extends WebView { @Override public void loadUrl(String url) { + mAnimationTime = 0; + mLoadTime = 0; if (!url.startsWith("http://") && !url.startsWith("file://")) { url = "http://" + url; } |