diff options
123 files changed, 4043 insertions, 846 deletions
@@ -380,6 +380,8 @@ sample_dir := development/samples # (see development/build/sdk.atree) web_docs_sample_code_flags := \ -hdf android.hasSamples 1 \ + -samplecode $(sample_dir)/AccessibilityService \ + resources/samples/AccessibilityService "Accessibility Service" \ -samplecode $(sample_dir)/ApiDemos \ resources/samples/ApiDemos "API Demos" \ -samplecode $(sample_dir)/BackupRestore \ diff --git a/api/current.xml b/api/current.xml index 6950d8c..dd39281 100644 --- a/api/current.xml +++ b/api/current.xml @@ -20079,6 +20079,17 @@ visibility="public" > </method> +<method name="isRunning" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="removeUpdateListener" return="void" abstract="false" @@ -20092,6 +20103,17 @@ <parameter name="listener" type="android.animation.Animator.AnimatorUpdateListener"> </parameter> </method> +<method name="reverse" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="setCurrentPlayTime" return="void" abstract="false" @@ -26597,6 +26619,17 @@ visibility="public" > </method> +<method name="invalidateOptionsMenu" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="isShowing" return="boolean" abstract="false" @@ -36145,8 +36178,6 @@ > <parameter name="appWidgetIds" type="int[]"> </parameter> -<parameter name="views" type="android.widget.RemoteViews"> -</parameter> <parameter name="viewId" type="int"> </parameter> </method> @@ -36162,9 +36193,37 @@ > <parameter name="appWidgetId" type="int"> </parameter> +<parameter name="viewId" type="int"> +</parameter> +</method> +<method name="partiallyUpdateAppWidget" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="appWidgetIds" type="int[]"> +</parameter> <parameter name="views" type="android.widget.RemoteViews"> </parameter> -<parameter name="viewId" type="int"> +</method> +<method name="partiallyUpdateAppWidget" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="appWidgetId" type="int"> +</parameter> +<parameter name="views" type="android.widget.RemoteViews"> </parameter> </method> <method name="updateAppWidget" @@ -103089,6 +103148,17 @@ visibility="public" > </field> +<field name="ERROR_CANNOT_RESUME" + type="int" + transient="false" + volatile="false" + value="1008" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ERROR_DEVICE_NOT_FOUND" type="int" transient="false" @@ -128150,6 +128220,16 @@ visibility="public" > </field> +<field name="SERIAL" + type="java.lang.String" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="TAGS" type="java.lang.String" transient="false" @@ -138894,6 +138974,66 @@ visibility="public" > </constructor> +<field name="fragment" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="fragmentArguments" + type="android.os.Bundle" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="icon" + type="android.graphics.drawable.Drawable" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="iconRes" + type="int" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="summary" + type="java.lang.CharSequence" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="title" + type="java.lang.CharSequence" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="PreferenceCategory" extends="android.preference.PreferenceGroup" @@ -217349,6 +217489,30 @@ visibility="public" > </method> +<method name="onRestoreInstanceState" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="state" type="android.os.Parcelable"> +</parameter> +</method> +<method name="onSaveInstanceState" + return="android.os.Parcelable" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="setAdapter" return="void" abstract="false" @@ -228190,6 +228354,32 @@ <parameter name="visibility" type="int"> </parameter> </method> +<method name="showNext" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="viewId" type="int"> +</parameter> +</method> +<method name="showPrevious" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="viewId" type="int"> +</parameter> +</method> <method name="writeToParcel" return="void" abstract="false" @@ -228387,6 +228577,17 @@ visibility="public" > </method> +<method name="onDataSetChanged" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="onDestroy" return="void" abstract="true" diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 7a7f8ed..9fe1fb8 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -49,6 +49,9 @@ #include "BootAnimation.h" +#define USER_BOOTANIMATION_FILE "/data/local/bootanimation.zip" +#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip" + namespace android { // --------------------------------------------------------------------------- @@ -244,12 +247,12 @@ status_t BootAnimation::readyToRun() { mFlingerSurfaceControl = control; mFlingerSurface = s; - mAndroidAnimation = false; - status_t err = mZip.open("/data/local/bootanimation.zip"); - if (err != NO_ERROR) { - err = mZip.open("/system/media/bootanimation.zip"); - if (err != NO_ERROR) { - mAndroidAnimation = true; + mAndroidAnimation = true; + if ((access(USER_BOOTANIMATION_FILE, R_OK) == 0) || + (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0)) { + if ((mZip.open(USER_BOOTANIMATION_FILE) != NO_ERROR) || + (mZip.open(SYSTEM_BOOTANIMATION_FILE) != NO_ERROR)) { + mAndroidAnimation = false; } } diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index 1013e01..cd6531b 100755 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -422,7 +422,6 @@ public class Animator extends Animatable { mEvaluator = (mValueType == int.class) ? sIntEvaluator : (mValueType == double.class) ? sDoubleEvaluator : sFloatEvaluator; } - mPlayingBackwards = false; mCurrentIteration = 0; mInitialized = true; } @@ -790,7 +789,8 @@ public class Animator extends Animatable { } } - public void start() { + private void start(boolean playBackwards) { + mPlayingBackwards = playBackwards; mPlayingState = STOPPED; sPendingAnimations.add(this); if (sAnimationHandler == null) { @@ -801,6 +801,12 @@ public class Animator extends Animatable { sAnimationHandler.sendEmptyMessage(ANIMATION_START); } + @Override + public void start() { + start(false); + } + + @Override public void cancel() { if (mListeners != null) { ArrayList<AnimatableListener> tmpListeners = @@ -814,6 +820,7 @@ public class Animator extends Animatable { mPlayingState = CANCELED; } + @Override public void end() { // Just set the ENDED flag - this causes the animation to end the next time a frame // is processed. @@ -821,6 +828,33 @@ public class Animator extends Animatable { } /** + * Returns whether this Animator is currently running (having been started and not yet ended). + * @return Wehther the Animator is running. + */ + public boolean isRunning() { + return mPlayingState == RUNNING; + } + + /** + * Plays the Animator in reverse. If the animation is already running, + * it will stop itself and play backwards from the point reached when reverse was called. + * If the animation is not currently running, then it will start from the end and + * play backwards. This behavior is only set for the current animation; future playing + * of the animation will use the default behavior of playing forward. + */ + public void reverse() { + mPlayingBackwards = !mPlayingBackwards; + if (mPlayingState == RUNNING) { + long currentTime = AnimationUtils.currentAnimationTimeMillis(); + long currentPlayTime = currentTime - mStartTime; + long timeLeft = mDuration - currentPlayTime; + mStartTime = currentTime - timeLeft; + } else { + start(true); + } + } + + /** * Called internally to end an animation by removing it from the animations list. Must be * called on the UI thread. */ @@ -892,7 +926,6 @@ public class Animator extends Animatable { * <code>repeatCount</code> has been exceeded and the animation should be ended. */ private boolean animationFrame(long currentTime) { - boolean done = false; if (mPlayingState == STOPPED) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d66e98b..d5741fc 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -365,7 +365,8 @@ public class ActivityManager { /** * The time when the service was first made active, either by someone - * starting or binding to it. + * starting or binding to it. This + * is in units of {@link android.os.SystemClock#elapsedRealtime()}. */ public long activeSince; @@ -387,7 +388,8 @@ public class ActivityManager { /** * The time when there was last activity in the service (either - * explicit requests to start it or clients binding to it). + * explicit requests to start it or clients binding to it). This + * is in units of {@link android.os.SystemClock#uptimeMillis()}. */ public long lastActivityTime; diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index b4c138e..274a266 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -228,6 +228,9 @@ public class Dialog implements DialogInterface, Window.Callback, public void show() { if (mShowing) { if (mDecor != null) { + if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { + mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); + } mDecor.setVisibility(View.VISIBLE); } return; @@ -792,6 +795,13 @@ public class Dialog implements DialogInterface, Window.Callback, } /** + * @see Activity#invalidateOptionsMenu() + */ + public void invalidateOptionsMenu() { + mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL); + } + + /** * @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) */ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 6011eec..7730942 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -62,11 +62,10 @@ public class AppWidgetHost { msg.sendToTarget(); } - public void viewDataChanged(int appWidgetId, RemoteViews views, int viewId) { + public void viewDataChanged(int appWidgetId, int viewId) { Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED); msg.arg1 = appWidgetId; msg.arg2 = viewId; - msg.obj = views; msg.sendToTarget(); } } @@ -87,7 +86,7 @@ public class AppWidgetHost { break; } case HANDLE_VIEW_DATA_CHANGED: { - viewDataChanged(msg.arg1, (RemoteViews) msg.obj, msg.arg2); + viewDataChanged(msg.arg1, msg.arg2); break; } } @@ -264,13 +263,13 @@ public class AppWidgetHost { } } - void viewDataChanged(int appWidgetId, RemoteViews views, int viewId) { + void viewDataChanged(int appWidgetId, int viewId) { AppWidgetHostView v; synchronized (mViews) { v = mViews.get(appWidgetId); } if (v != null) { - v.viewDataChanged(views, viewId); + v.viewDataChanged(viewId); } } } diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 22f4266..4f8ee93 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -264,7 +264,7 @@ public class AppWidgetHostView extends FrameLayout { * Process data-changed notifications for the specified view in the specified * set of {@link RemoteViews} views. */ - void viewDataChanged(RemoteViews remoteViews, int viewId) { + void viewDataChanged(int viewId) { View v = findViewById(viewId); if ((v != null) && (v instanceof AdapterView<?>)) { AdapterView<?> adapterView = (AdapterView<?>) v; diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 5ee721f..2a583c1 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -233,6 +233,10 @@ public class AppWidgetManager { /** * Set the RemoteViews to use for the specified appWidgetIds. * + * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should + * contain a complete representation of the widget. For performing partial widget updates, see + * {@link #partiallyUpdateAppWidget(int[], RemoteViews)}. + * * <p> * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, * and outside of the handler. @@ -253,6 +257,10 @@ public class AppWidgetManager { /** * Set the RemoteViews to use for the specified appWidgetId. * + * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should + * contain a complete representation of the widget. For performing partial widget updates, see + * {@link #partiallyUpdateAppWidget(int, RemoteViews)}. + * * <p> * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, * and outside of the handler. @@ -266,6 +274,59 @@ public class AppWidgetManager { } /** + * Perform an incremental update or command on the widget(s) specified by appWidgetIds. + * + * This update differs from {@link #updateAppWidget(int[], RemoteViews)} in that the + * RemoteViews object which is passed is understood to be an incomplete representation of the + * widget, and hence is not cached by the AppWidgetService. Note that because these updates are + * not cached, any state that they modify that is not restored by restoreInstanceState will not + * persist in the case that the widgets are restored using the cached version in + * AppWidgetService. + * + * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)}, + * {@link RemoteViews#setScrollPosition(int, int)} and similar commands. + * + * <p> + * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, + * and outside of the handler. + * This method will only work when called from the uid that owns the AppWidget provider. + * + * @param appWidgetIds The AppWidget instances for which to set the RemoteViews. + * @param views The RemoteViews object containing the incremental update / command. + */ + public void partiallyUpdateAppWidget(int[] appWidgetIds, RemoteViews views) { + try { + sService.partiallyUpdateAppWidgetIds(appWidgetIds, views); + } catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } + } + + /** + * Perform an incremental update or command on the widget specified by appWidgetId. + * + * This update differs from {@link #updateAppWidget(int, RemoteViews)} in that the RemoteViews + * object which is passed is understood to be an incomplete representation of the widget, and + * hence is not cached by the AppWidgetService. Note that because these updates are not cached, + * any state that they modify that is not restored by restoreInstanceState will not persist in + * the case that the widgets are restored using the cached version in AppWidgetService. + * + * Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)}, + * {@link RemoteViews#setScrollPosition(int, int)} and similar commands. + * + * <p> + * It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast, + * and outside of the handler. + * This method will only work when called from the uid that owns the AppWidget provider. + * + * @param appWidgetId The AppWidget instance for which to set the RemoteViews. + * @param views The RemoteViews object containing the incremental update / command. + */ + public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views) { + partiallyUpdateAppWidget(new int[] { appWidgetId }, views); + } + + /** * Set the RemoteViews to use for all AppWidget instances for the supplied AppWidget provider. * * <p> @@ -292,12 +353,11 @@ public class AppWidgetManager { * to invalidate their currently data. * * @param appWidgetIds The AppWidget instances for which to notify of view data changes. - * @param views The RemoteViews which contains the view referenced at viewId. * @param viewId The collection view id. */ - public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, RemoteViews views, int viewId) { + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { try { - sService.notifyAppWidgetViewDataChanged(appWidgetIds, views, viewId); + sService.notifyAppWidgetViewDataChanged(appWidgetIds, viewId); } catch (RemoteException e) { throw new RuntimeException("system server dead?", e); @@ -309,11 +369,10 @@ public class AppWidgetManager { * to invalidate it's currently data. * * @param appWidgetId The AppWidget instance for which to notify of view data changes. - * @param views The RemoteViews which contains the view referenced at viewId. * @param viewId The collection view id. */ - public void notifyAppWidgetViewDataChanged(int appWidgetId, RemoteViews views, int viewId) { - notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, views, viewId); + public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) { + notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, viewId); } /** diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java index e69c324..447e642 100644 --- a/core/java/android/net/DownloadManager.java +++ b/core/java/android/net/DownloadManager.java @@ -185,6 +185,12 @@ public class DownloadManager { public final static int ERROR_DEVICE_NOT_FOUND = 1007; /** + * Value of {@link #COLUMN_ERROR_CODE} when some possibly transient error occurred but we can't + * resume the download. + */ + public final static int ERROR_CANNOT_RESUME = 1008; + + /** * Broadcast intent action sent by the download manager when a download completes. */ public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE"; @@ -715,7 +721,8 @@ public class DownloadManager { if (translateStatus(status) != STATUS_FAILED) { return 0; // arbitrary value when status is not an error } - if ((400 <= status && status < 490) || (500 <= status && status < 600)) { + if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS) + || (500 <= status && status < 600)) { // HTTP status code return status; } @@ -740,6 +747,9 @@ public class DownloadManager { case Downloads.STATUS_DEVICE_NOT_FOUND_ERROR: return ERROR_DEVICE_NOT_FOUND; + case Downloads.Impl.STATUS_CANNOT_RESUME: + return ERROR_CANNOT_RESUME; + default: return ERROR_UNKNOWN; } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index a699388..95f217f 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -23,6 +23,7 @@ import java.util.Map; import android.util.Log; import android.util.Printer; import android.util.SparseArray; +import android.util.TimeUtils; /** * A class providing access to battery usage statistics, including information on @@ -290,6 +291,11 @@ public abstract class BatteryStats implements Parcelable { */ public static abstract class Proc { + public static class ExcessiveWake { + public long overTime; + public long usedTime; + } + /** * Returns the total time (in 1/100 sec) spent executing in user code. * @@ -326,6 +332,10 @@ public abstract class BatteryStats implements Parcelable { * @see BatteryStats#getCpuSpeedSteps() */ public abstract long getTimeAtCpuSpeedStep(int speedStep, int which); + + public abstract int countExcessiveWakes(); + + public abstract ExcessiveWake getExcessiveWake(int i); } /** @@ -421,6 +431,8 @@ public abstract class BatteryStats implements Parcelable { public static final int STATE_BLUETOOTH_ON_FLAG = 1<<20; public static final int STATE_AUDIO_ON_FLAG = 1<<19; public static final int STATE_VIDEO_ON_FLAG = 1<<18; + public static final int STATE_WAKE_LOCK_FLAG = 1<<17; + public static final int STATE_SENSOR_ON_FLAG = 1<<16; public int states; @@ -470,6 +482,16 @@ public abstract class BatteryStats implements Parcelable { batteryVoltage = o.batteryVoltage; states = o.states; } + + public boolean same(HistoryItem o) { + return batteryLevel == o.batteryLevel + && batteryStatus == o.batteryStatus + && batteryHealth == o.batteryHealth + && batteryPlugType == o.batteryPlugType + && batteryTemperature == o.batteryTemperature + && batteryVoltage == o.batteryVoltage + && states == o.states; + } } public static final class BitDescription { @@ -633,6 +655,8 @@ public abstract class BatteryStats implements Parcelable { new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth"), new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio"), new BitDescription(HistoryItem.STATE_VIDEO_ON_FLAG, "video"), + new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock"), + new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor"), new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK, HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness", SCREEN_BRIGHTNESS_NAMES), @@ -1376,7 +1400,6 @@ public abstract class BatteryStats implements Parcelable { pw.println(getDischargeStartLevel()); pw.print(prefix); pw.print(" Discharge cycle current level: "); pw.println(getDischargeCurrentLevel()); - } else { pw.print(prefix); pw.println(" Device is currently plugged into power"); pw.print(prefix); pw.print(" Last discharge cycle start level: "); pw.println(getDischargeStartLevel()); @@ -1384,6 +1407,13 @@ public abstract class BatteryStats implements Parcelable { pw.println(getDischargeCurrentLevel()); } pw.println(" "); + } else { + pw.print(prefix); pw.println(" Device battery use since last full charge"); + pw.print(prefix); pw.print(" Amount discharged (lower bound): "); + pw.println(getLowDischargeAmountSinceCharge()); + pw.print(prefix); pw.print(" Amount discharged (upper bound): "); + pw.println(getHighDischargeAmountSinceCharge()); + pw.println(" "); } @@ -1524,12 +1554,16 @@ public abstract class BatteryStats implements Parcelable { long userTime; long systemTime; int starts; + int numExcessive; userTime = ps.getUserTime(which); systemTime = ps.getSystemTime(which); starts = ps.getStarts(which); + numExcessive = which == STATS_SINCE_CHARGED + ? ps.countExcessiveWakes() : 0; - if (userTime != 0 || systemTime != 0 || starts != 0) { + if (userTime != 0 || systemTime != 0 || starts != 0 + || numExcessive != 0) { sb.setLength(0); sb.append(prefix); sb.append(" Proc "); sb.append(ent.getKey()); sb.append(":\n"); @@ -1539,6 +1573,18 @@ public abstract class BatteryStats implements Parcelable { sb.append(prefix); sb.append(" "); sb.append(starts); sb.append(" proc starts"); pw.println(sb.toString()); + for (int e=0; e<numExcessive; e++) { + Uid.Proc.ExcessiveWake ew = ps.getExcessiveWake(e); + if (ew != null) { + pw.print(prefix); pw.print(" * Killed for wake lock use: "); + TimeUtils.formatDuration(ew.usedTime, pw); + pw.print(" over "); + TimeUtils.formatDuration(ew.overTime, pw); + pw.print(" ("); + pw.print((ew.usedTime*100)/ew.overTime); + pw.println("%)"); + } + } uidActivity = true; } } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 78ec638..8624467 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -62,6 +62,9 @@ public class Build { /** The name of the hardware (from the kernel command line or /proc). */ public static final String HARDWARE = getString("ro.hardware"); + /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */ + public static final String SERIAL = getString("ro.serialno"); + /** Various version strings. */ public static class VERSION { /** diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index a9d7342..d360140 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -192,10 +192,11 @@ public class Looper { pw.println(prefix + "mQueue=" + ((mQueue != null) ? mQueue : "(null")); if (mQueue != null) { synchronized (mQueue) { + long now = SystemClock.uptimeMillis(); Message msg = mQueue.mMessages; int n = 0; while (msg != null) { - pw.println(prefix + " Message " + n + ": " + msg); + pw.println(prefix + " Message " + n + ": " + msg.toString(now)); n++; msg = msg.next; } diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index 476da1d..49b72fe 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -19,6 +19,7 @@ package android.os; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.TimeUtils; /** * @@ -366,13 +367,17 @@ public final class Message implements Parcelable { } public String toString() { + return toString(SystemClock.uptimeMillis()); + } + + String toString(long now) { StringBuilder b = new StringBuilder(); b.append("{ what="); b.append(what); b.append(" when="); - b.append(when); + TimeUtils.formatDuration(when-now, b); if (arg1 != 0) { b.append(" arg1="); diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index 6b00690..03fc399 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -194,13 +194,13 @@ public abstract class PreferenceActivity extends ListActivity implements } }; - private class HeaderViewHolder { - ImageView icon; - TextView title; - TextView summary; - } + private static class HeaderAdapter extends ArrayAdapter<Header> { + private static class HeaderViewHolder { + ImageView icon; + TextView title; + TextView summary; + } - private class HeaderAdapter extends ArrayAdapter<Header> { private LayoutInflater mInflater; public HeaderAdapter(Context context, List<Header> objects) { @@ -217,23 +217,31 @@ public abstract class PreferenceActivity extends ListActivity implements view = mInflater.inflate(com.android.internal.R.layout.preference_list_item, parent, false); holder = new HeaderViewHolder(); - holder.icon = (ImageView)view.findViewById( - com.android.internal.R.id.icon); - holder.title = (TextView)view.findViewById( - com.android.internal.R.id.title); - holder.summary = (TextView)view.findViewById( - com.android.internal.R.id.summary); + holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon); + holder.title = (TextView) view.findViewById(com.android.internal.R.id.title); + holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary); view.setTag(holder); } else { view = convertView; - holder = (HeaderViewHolder)view.getTag(); + holder = (HeaderViewHolder) view.getTag(); } + // All view fields must be updated every time, because the view may be recycled Header header = getItem(position); - if (header.icon != null) holder.icon.setImageDrawable(header.icon); - else if (header.iconRes != 0) holder.icon.setImageResource(header.iconRes); - if (header.title != null) holder.title.setText(header.title); - if (header.summary != null) holder.summary.setText(header.summary); + if (header.icon == null) { + holder.icon.setImageDrawable(null); + holder.icon.setImageResource(header.iconRes); + } else { + holder.icon.setImageResource(0); + holder.icon.setImageDrawable(header.icon); + } + holder.title.setText(header.title); + if (TextUtils.isEmpty(header.summary)) { + holder.summary.setVisibility(View.GONE); + } else { + holder.summary.setVisibility(View.VISIBLE); + holder.summary.setText(header.summary); + } return view; } @@ -247,38 +255,38 @@ public abstract class PreferenceActivity extends ListActivity implements * Title of the header that is shown to the user. * @attr ref android.R.styleable#PreferenceHeader_title */ - CharSequence title; + public CharSequence title; /** * Optional summary describing what this header controls. * @attr ref android.R.styleable#PreferenceHeader_summary */ - CharSequence summary; + public CharSequence summary; /** * Optional icon resource to show for this header. * @attr ref android.R.styleable#PreferenceHeader_icon */ - int iconRes; + public int iconRes; /** * Optional icon drawable to show for this header. (If this is non-null, * the iconRes will be ignored.) */ - Drawable icon; + public Drawable icon; /** * Full class name of the fragment to display when this header is * selected. * @attr ref android.R.styleable#PreferenceHeader_fragment */ - String fragment; + public String fragment; /** * Optional arguments to supply to the fragment when it is * instantiated. */ - Bundle fragmentArguments; + public Bundle fragmentArguments; } @Override diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java new file mode 100644 index 0000000..37f1ff2 --- /dev/null +++ b/core/java/android/provider/BrowserContract.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.provider; + +import android.accounts.Account; +import android.content.ContentProviderClient; +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.RemoteException; +import android.provider.SyncStateContract; +import android.util.Pair; + +/** + * @hide + */ +public class BrowserContract { + /** The authority for the browser provider */ + public static final String AUTHORITY = "com.android.browser"; + + /** A content:// style uri to the authority for the browser provider */ + public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); + + /** + * An optional insert, update or delete URI parameter that allows the caller + * to specify that it is a sync adapter. The default value is false. If true + * the dirty flag is not automatically set and the "syncToNetwork" parameter + * is set to false when calling + * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}. + */ + public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter"; + + /** + * Generic columns for use by sync adapters. The specific functions of + * these columns are private to the sync adapter. Other clients of the API + * should not attempt to either read or write these columns. + */ + interface BaseSyncColumns { + /** Generic column for use by sync adapters. */ + public static final String SYNC1 = "sync1"; + /** Generic column for use by sync adapters. */ + public static final String SYNC2 = "sync2"; + /** Generic column for use by sync adapters. */ + public static final String SYNC3 = "sync3"; + /** Generic column for use by sync adapters. */ + public static final String SYNC4 = "sync4"; + /** Generic column for use by sync adapters. */ + public static final String SYNC5 = "sync5"; + } + + /** + * Convenience definitions for use in implementing chrome bookmarks sync in the Bookmarks table. + */ + public static final class ChromeSyncColumns { + private ChromeSyncColumns() {} + + /** The server unique ID for an item */ + public static final String SERVER_UNIQUE = BaseSyncColumns.SYNC3; + + public static final String FOLDER_NAME_ROOT = "google_chrome"; + public static final String FOLDER_NAME_BOOKMARKS = "google_chrome_bookmarks"; + public static final String FOLDER_NAME_BOOKMARKS_BAR = "bookmark_bar"; + public static final String FOLDER_NAME_OTHER_BOOKMARKS = "other_bookmarks"; + + /** The client unique ID for an item */ + public static final String CLIENT_UNIQUE = BaseSyncColumns.SYNC4; + } + + /** + * Columns that appear when each row of a table belongs to a specific + * account, including sync information that an account may need. + */ + interface SyncColumns extends BaseSyncColumns { + /** + * The name of the account instance to which this row belongs, which when paired with + * {@link #ACCOUNT_TYPE} identifies a specific account. + * <P>Type: TEXT</P> + */ + public static final String ACCOUNT_NAME = "account_name"; + + /** + * The type of account to which this row belongs, which when paired with + * {@link #ACCOUNT_NAME} identifies a specific account. + * <P>Type: TEXT</P> + */ + public static final String ACCOUNT_TYPE = "account_type"; + + /** + * String that uniquely identifies this row to its source account. + * <P>Type: TEXT</P> + */ + public static final String SOURCE_ID = "sourceid"; + + /** + * Version number that is updated whenever this row or its related data + * changes. + * <P>Type: INTEGER</P> + */ + public static final String VERSION = "version"; + + /** + * Flag indicating that {@link #VERSION} has changed, and this row needs + * to be synchronized by its owning account. + * <P>Type: INTEGER (boolean)</P> + */ + public static final String DIRTY = "dirty"; + } + + interface BookmarkColumns { + /** + * The unique ID for a row. + * <P>Type: INTEGER (long)</P> + */ + public static final String _ID = "_id"; + + /** + * The URL of the bookmark. + * <P>Type: TEXT (URL)</P> + */ + public static final String URL = "url"; + + /** + * The user visible title of the bookmark. + * <P>Type: TEXT</P> + */ + public static final String TITLE = "title"; + + /** + * The favicon of the bookmark, may be NULL. + * Must decode via {@link BitmapFactory#decodeByteArray}. + * <p>Type: BLOB (image)</p> + */ + public static final String FAVICON = "favicon"; + + /** + * A thumbnail of the page,may be NULL. + * Must decode via {@link BitmapFactory#decodeByteArray}. + * <p>Type: BLOB (image)</p> + */ + public static final String THUMBNAIL = "thumbnail"; + + /** + * The touch icon for the web page, may be NULL. + * Must decode via {@link BitmapFactory#decodeByteArray}. + * <p>Type: BLOB (image)</p> + * @hide + */ + public static final String TOUCH_ICON = "touch_icon"; + } + + /** + * The bookmarks table, which holds the user's browser bookmarks. + */ + public static final class Bookmarks implements BookmarkColumns, SyncColumns { + /** + * This utility class cannot be instantiated. + */ + private Bookmarks() {} + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks"); + + /** + * The content:// style URI for the default folder + */ + public static final Uri CONTENT_URI_DEFAULT_FOLDER = + Uri.withAppendedPath(CONTENT_URI, "folder"); + + /** + * Builds a URI that points to a specific folder. + * @param folderId the ID of the folder to point to + */ + public static final Uri buildFolderUri(long folderId) { + return ContentUris.withAppendedId(CONTENT_URI_DEFAULT_FOLDER, folderId); + } + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of bookmarks. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/bookmark"; + + /** + * The MIME type of a {@link #CONTENT_URI} of a single bookmark. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/bookmark"; + + /** + * Query parameter to use if you want to see deleted bookmarks that are still + * around on the device and haven't been synced yet. + * @see #IS_DELETED + */ + public static final String QUERY_PARAMETER_SHOW_DELETED = "show_deleted"; + + /** + * Flag indicating if an item is a folder or bookmark. Non-zero values indicate + * a folder and zero indicates a bookmark. + * <P>Type: INTEGER (boolean)</P> + */ + public static final String IS_FOLDER = "folder"; + + /** + * The ID of the parent folder. ID 0 is the root folder. + * <P>Type: INTEGER (reference to item in the same table)</P> + */ + public static final String PARENT = "parent"; + + /** + * The position of the bookmark in relation to it's siblings that share the same + * {@link #PARENT}. May be negative. + * <P>Type: INTEGER</P> + */ + public static final String POSITION = "position"; + + /** + * The item that the bookmark should be inserted after. + * May be negative. + * <P>Type: INTEGER</P> + */ + public static final String INSERT_AFTER = "insert_after"; + + /** + * A flag to indicate if an item has been deleted. Queries will not return deleted + * entries unless you add the {@link #QUERY_PARAMETER_SHOW_DELETED} query paramter + * to the URI when performing your query. + * <p>Type: INTEGER (non-zero if the item has been deleted, zero if it hasn't) + * @see #QUERY_PARAMETER_SHOW_DELETED + */ + public static final String IS_DELETED = "deleted"; + } + + /** + * The history table, which holds the browsing history. + */ + public static final class History implements BookmarkColumns { + /** + * This utility class cannot be instantiated. + */ + private History() {} + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "history"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of browser history items. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/browser-history"; + + /** + * The MIME type of a {@link #CONTENT_URI} of a single browser history item. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/browser-history"; + + /** + * The date the item was last visited, in milliseconds since the epoch. + * <p>Type: INTEGER (date in milliseconds since January 1, 1970)</p> + */ + public static final String DATE_LAST_VISITED = "date"; + + /** + * The date the item created, in milliseconds since the epoch. + * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p> + */ + public static final String DATE_CREATED = "created"; + + /** + * The number of times the item has been visited. + * <p>Type: INTEGER</p> + */ + public static final String VISITS = "visits"; + } + + /** + * The search history table. + * @hide + */ + public static final class Searches { + private Searches() {} + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "searches"); + + /** + * The MIME type of {@link #CONTENT_URI} providing a directory of browser search items. + */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searches"; + + /** + * The MIME type of a {@link #CONTENT_URI} of a single browser search item. + */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/searches"; + + /** + * The unique ID for a row. + * <P>Type: INTEGER (long)</P> + */ + public static final String _ID = "_id"; + + /** + * The user entered search term. + */ + public static final String SEARCH = "search"; + + /** + * The date the search was performed, in milliseconds since the epoch. + * <p>Type: NUMBER (date in milliseconds since January 1, 1970)</p> + */ + public static final String DATE = "date"; + } + + /** + * A table provided for sync adapters to use for storing private sync state data. + * + * @see SyncStateContract + */ + public static final class SyncState implements SyncStateContract.Columns { + /** + * This utility class cannot be instantiated + */ + private SyncState() {} + + public static final String CONTENT_DIRECTORY = + SyncStateContract.Constants.CONTENT_DIRECTORY; + + /** + * The content:// style URI for this table + */ + public static final Uri CONTENT_URI = + Uri.withAppendedPath(AUTHORITY_URI, CONTENT_DIRECTORY); + + /** + * @see android.provider.SyncStateContract.Helpers#get + */ + public static byte[] get(ContentProviderClient provider, Account account) + throws RemoteException { + return SyncStateContract.Helpers.get(provider, CONTENT_URI, account); + } + + /** + * @see android.provider.SyncStateContract.Helpers#get + */ + public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Account account) + throws RemoteException { + return SyncStateContract.Helpers.getWithUri(provider, CONTENT_URI, account); + } + + /** + * @see android.provider.SyncStateContract.Helpers#set + */ + public static void set(ContentProviderClient provider, Account account, byte[] data) + throws RemoteException { + SyncStateContract.Helpers.set(provider, CONTENT_URI, account, data); + } + + /** + * @see android.provider.SyncStateContract.Helpers#newSetOperation + */ + public static ContentProviderOperation newSetOperation(Account account, byte[] data) { + return SyncStateContract.Helpers.newSetOperation(CONTENT_URI, account, data); + } + } +} diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index c9b5512..6bf0d5b 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -1060,6 +1060,16 @@ public final class Downloads { public static final int STATUS_PRECONDITION_FAILED = 412; /** + * The lowest-valued error status that is not an actual HTTP status code. + */ + public static final int MIN_ARTIFICIAL_ERROR_STATUS = 489; + + /** + * Some possibly transient error occurred, but we can't resume the download. + */ + public static final int STATUS_CANNOT_RESUME = 489; + + /** * This download was canceled */ public static final int STATUS_CANCELED = 490; diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 0fc70d5..b01a71d 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.PrintWriter; import java.util.TimeZone; import java.util.Date; @@ -130,4 +131,128 @@ public class TimeUtils { public static String getTimeZoneDatabaseVersion() { return ZoneInfoDB.getVersion(); } + + private static final int SECONDS_PER_MINUTE = 60; + private static final int SECONDS_PER_HOUR = 60 * 60; + private static final int SECONDS_PER_DAY = 24 * 60 * 60; + + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, StringBuilder builder) { + if (duration == 0) { + builder.append("0"); + return; + } + if (duration > 0) { + builder.append("+"); + } else { + builder.append("-"); + duration = -duration; + } + + int millis = (int)(duration%1000); + int seconds = (int) Math.floor(duration / 1000); + int days = 0, hours = 0, minutes = 0; + + if (seconds > SECONDS_PER_DAY) { + days = seconds / SECONDS_PER_DAY; + seconds -= days * SECONDS_PER_DAY; + } + if (seconds > SECONDS_PER_HOUR) { + hours = seconds / SECONDS_PER_HOUR; + seconds -= hours * SECONDS_PER_HOUR; + } + if (seconds > SECONDS_PER_MINUTE) { + minutes = seconds / SECONDS_PER_MINUTE; + seconds -= minutes * SECONDS_PER_MINUTE; + } + + boolean doall = false; + if (days > 0) { + builder.append(days); + builder.append('d'); + doall = true; + } + if (doall || hours > 0) { + builder.append(hours); + builder.append('h'); + doall = true; + } + if (doall || minutes > 0) { + builder.append(minutes); + builder.append('m'); + doall = true; + } + if (doall || seconds > 0) { + builder.append(seconds); + builder.append('s'); + doall = true; + } + builder.append(millis); + builder.append("ms"); + } + + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, PrintWriter pw) { + if (duration == 0) { + pw.print("0"); + return; + } + if (duration > 0) { + pw.print("+"); + } else { + pw.print("-"); + duration = -duration; + } + + int millis = (int)(duration%1000); + int seconds = (int) Math.floor(duration / 1000); + int days = 0, hours = 0, minutes = 0; + + if (seconds > SECONDS_PER_DAY) { + days = seconds / SECONDS_PER_DAY; + seconds -= days * SECONDS_PER_DAY; + } + if (seconds > SECONDS_PER_HOUR) { + hours = seconds / SECONDS_PER_HOUR; + seconds -= hours * SECONDS_PER_HOUR; + } + if (seconds > SECONDS_PER_MINUTE) { + minutes = seconds / SECONDS_PER_MINUTE; + seconds -= minutes * SECONDS_PER_MINUTE; + } + + boolean doall = false; + if (days > 0) { + pw.print(days); + pw.print('d'); + doall = true; + } + if (doall || hours > 0) { + pw.print(hours); + pw.print('h'); + doall = true; + } + if (doall || minutes > 0) { + pw.print(minutes); + pw.print('m'); + doall = true; + } + if (doall || seconds > 0) { + pw.print(seconds); + pw.print('s'); + doall = true; + } + pw.print(millis); + pw.print("ms"); + } + + + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long time, long now, PrintWriter pw) { + if (time == 0) { + pw.print("--"); + return; + } + formatDuration(time-now, pw); + } } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index fabe5c8..34d7935 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -27,7 +27,8 @@ public class Display /** - * Use the WindowManager interface to create a Display object. + * Use {@link android.view.WindowManager#getDefaultDisplay() + * WindowManager.getDefaultDisplay()} to create a Display object. * Display gives you access to some information about a particular display * connected to the device. */ diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 44bd6d4..ca60a89 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -385,7 +385,7 @@ public abstract class HardwareRenderer { onPreDraw(); Canvas canvas = mCanvas; - int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + int saveCount = canvas.save(); canvas.translate(0, -yOffset); try { diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java index a959e0d..d0985d9 100644 --- a/core/java/android/view/MenuInflater.java +++ b/core/java/android/view/MenuInflater.java @@ -246,8 +246,9 @@ public class MenuInflater { * - 0: never * - 1: ifRoom * - 2: always + * - -1: Safe sentinel for "no value". */ - private int itemShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER; + private int itemShowAsAction; private String itemListenerMethodName; @@ -322,7 +323,7 @@ public class MenuInflater { itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked); itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible); itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled); - itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, 0); + itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, -1); itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick); a.recycle(); @@ -346,8 +347,11 @@ public class MenuInflater { .setTitleCondensed(itemTitleCondensed) .setIcon(itemIconResId) .setAlphabeticShortcut(itemAlphabeticShortcut) - .setNumericShortcut(itemNumericShortcut) - .setShowAsAction(itemShowAsAction); + .setNumericShortcut(itemNumericShortcut); + + if (itemShowAsAction >= 0) { + item.setShowAsAction(itemShowAsAction); + } if (itemListenerMethodName != null) { if (mContext.isRestricted()) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 570793b..a0e840d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6630,9 +6630,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @see android.widget.ScrollBarDrawable * @hide */ - protected void onDrawHorizontalScrollBar(Canvas canvas, - Drawable scrollBar, - int l, int t, int r, int b) { + protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar, + int l, int t, int r, int b) { scrollBar.setBounds(l, t, r, b); scrollBar.draw(canvas); } @@ -6651,9 +6650,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @see android.widget.ScrollBarDrawable * @hide */ - protected void onDrawVerticalScrollBar(Canvas canvas, - Drawable scrollBar, - int l, int t, int r, int b) { + protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, + int l, int t, int r, int b) { scrollBar.setBounds(l, t, r, b); scrollBar.draw(canvas); } @@ -10018,8 +10016,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility public final Interpolator scrollBarInterpolator = new Interpolator(1, 2); - private final float[] mOpaque = {255.0f}; - private final float[] mTransparent = {0.0f}; + private final float[] mOpaque = { 255.0f }; + private final float[] mTransparent = { 0.0f }; /** * When fading should start. This time moves into the future every time diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index faa4783..d32ccb1 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -1363,7 +1363,6 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn ", metrics=" + cxt.getResources().getDisplayMetrics() + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); } - int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); try { canvas.translate(0, -yoff); if (mTranslator != null) { @@ -1374,7 +1373,6 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn mView.draw(canvas); } finally { mAttachInfo.mIgnoreDirtyState = false; - canvas.restoreToCount(saveCount); } if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index ef00d88..f11c5c6 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -17,8 +17,6 @@ package android.widget; import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; import android.animation.PropertyAnimator; import android.content.Context; @@ -27,11 +25,11 @@ import android.content.res.TypedArray; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; +import android.os.Parcel; +import android.os.Parcelable; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -98,15 +96,6 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> int mCurrentWindowStartUnbounded = 0; /** - * Indicates whether to treat the adapter to be a circular structure, ie. - * the view before 0 is considered to be <code>mAdapter.getCount() - 1</code> - * - * TODO: this doesn't do anything yet - * - */ - boolean mCycleViews = false; - - /** * Handler to post events to the main thread */ Handler mMainQueue; @@ -132,6 +121,12 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> boolean mFirstTime = true; /** + * Specifies if the animator should wrap from 0 to the end and vice versa + * or have hard boundaries at the beginning and end + */ + boolean mShouldLoop = true; + + /** * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit. */ Animation mInAnimation; @@ -140,7 +135,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> public AdapterViewAnimator(Context context) { super(context); - initViewAnimator(context, null); + initViewAnimator(); } public AdapterViewAnimator(Context context, AttributeSet attrs) { @@ -165,13 +160,13 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> a.recycle(); - initViewAnimator(context, attrs); + initViewAnimator(); } /** * Initialize this {@link AdapterViewAnimator} */ - private void initViewAnimator(Context context, AttributeSet attrs) { + private void initViewAnimator() { mMainQueue = new Handler(Looper.myLooper()); mActiveViews = new View[mNumActiveViews]; mPreviousViews = new ArrayList<View>(); @@ -183,13 +178,15 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> * desired number of views, and specify the offset * * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup} - * @param activeOffset This parameter specifies where the current index ({@link mWhichChild}) + * @param activeOffset This parameter specifies where the current index ({@link #mWhichChild}) * sits within the window. For example if activeOffset is 1, and numVisibleViews is 3, - * and {@link setDisplayedChild} is called with 10, then the effective window will be - * the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the + * and {@link #setDisplayedChild(int)} is called with 10, then the effective window will + * be the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the * window would instead contain indexes 10, 11 and 12. + * @param shouldLoop If the animator is show view 0, and setPrevious() is called, do we + * we loop back to the end, or do we do nothing */ - void configureViewAnimator(int numVisibleViews, int activeOffset) { + void configureViewAnimator(int numVisibleViews, int activeOffset, boolean shouldLoop) { if (activeOffset > numVisibleViews - 1) { // Throw an exception here. } @@ -200,6 +197,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> removeAllViewsInLayout(); mCurrentWindowStart = 0; mCurrentWindowEnd = -1; + mShouldLoop = shouldLoop; } /** @@ -215,6 +213,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> void animateViewForTransition(int fromIndex, int toIndex, View view) { PropertyAnimator pa; if (fromIndex == -1) { + view.setAlpha(0.0f); pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f); pa.start(); } else if (toIndex == -1) { @@ -232,9 +231,9 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> if (mAdapter != null) { mWhichChild = whichChild; if (whichChild >= mAdapter.getCount()) { - mWhichChild = 0; + mWhichChild = mShouldLoop ? 0 : mAdapter.getCount() - 1; } else if (whichChild < 0) { - mWhichChild = mAdapter.getCount() - 1; + mWhichChild = mShouldLoop ? mAdapter.getCount() - 1 : 0; } boolean hasFocus = getFocusedChild() != null; @@ -325,9 +324,12 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> } private LayoutParams createOrReuseLayoutParams(View v) { - final LayoutParams currentLp = (LayoutParams) v.getLayoutParams(); + final ViewGroup.LayoutParams currentLp = v.getLayoutParams(); if (currentLp instanceof LayoutParams) { - return currentLp; + LayoutParams lp = (LayoutParams) currentLp; + lp.setHorizontalOffset(0); + lp.setVerticalOffset(0); + return lp; } return new LayoutParams(v); } @@ -362,6 +364,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> int previousViewRelativeIndex = modulo(index - mCurrentWindowStart, mNumActiveViews); animateViewForTransition(previousViewRelativeIndex, -1, previousView); + mActiveViews[index] = null; } } } @@ -468,6 +471,66 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> mDataChanged = false; } + static class SavedState extends BaseSavedState { + int whichChild; + + /** + * Constructor called from {@link AdapterViewAnimator#onSaveInstanceState()} + */ + SavedState(Parcelable superState, int whichChild) { + super(superState); + this.whichChild = whichChild; + } + + /** + * Constructor called from {@link #CREATOR} + */ + private SavedState(Parcel in) { + super(in); + whichChild = in.readInt(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(whichChild); + } + + @Override + public String toString() { + return "AdapterViewAnimator.SavedState{ whichChild = " + whichChild + " }"; + } + + public static final Parcelable.Creator<SavedState> CREATOR + = new Parcelable.Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + return new SavedState(superState, mWhichChild); + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + + // Here we set mWhichChild in addition to setDisplayedChild + // We do the former in case mAdapter is null, and hence setDisplayedChild won't + // set mWhichChild + mWhichChild = ss.whichChild; + setDisplayedChild(mWhichChild); + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int count = getChildCount(); @@ -667,6 +730,24 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> } } + private final Rect dirtyRect = new Rect(); + @Override + public void removeViewInLayout(View view) { + // TODO: need to investigate this block a bit more + // and perhaps fix some other invalidations issues. + View parent = null; + view.setVisibility(INVISIBLE); + if (view.getLayoutParams() instanceof LayoutParams) { + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + parent = lp.getParentAndDirtyRegion(dirtyRect); + } + + super.removeViewInLayout(view); + + if (parent != null) + parent.invalidate(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom); + } + static class LayoutParams extends ViewGroup.LayoutParams { int horizontalOffset; int verticalOffset; @@ -701,6 +782,25 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> p.invalidate(r.left, r.top, r.right, r.bottom); } + public View getParentAndDirtyRegion(Rect globalRect) { + globalRect.set(mView.getLeft(), mView.getTop(), mView.getRight(), mView.getBottom()); + View p = mView; + boolean firstPass = true; + parentRect.set(0, 0, 0, 0); + while (p.getParent() != null && p.getParent() instanceof View + && !parentRect.contains(globalRect)) { + if (!firstPass) { + globalRect.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY()); + } + + firstPass = false; + p = (View) p.getParent(); + parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(), + p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY()); + } + return p; + } + private Rect invalidateRect = new Rect(); // This is public so that PropertyAnimator can access it public void setVerticalOffset(int newVerticalOffset) { diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java index 901c761..895683d 100644 --- a/core/java/android/widget/AdapterViewFlipper.java +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -25,8 +25,8 @@ import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.util.Log; +import android.view.RemotableViewMethod; import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; import android.widget.RemoteViews.RemoteView; /** @@ -163,6 +163,40 @@ public class AdapterViewFlipper extends AdapterViewAnimator { } /** + * {@inheritDoc} + */ + @Override + @RemotableViewMethod + public void showNext() { + // if the flipper is currently flipping automatically, and showNext() is called + // we should we should make sure to reset the timer + if (mRunning) { + mHandler.removeMessages(FLIP_MSG); + Message msg = mHandler.obtainMessage(FLIP_MSG); + mHandler.sendMessageDelayed(msg, mFlipInterval); + } + super.showNext(); + } + + /** + * {@inheritDoc} + */ + @Override + @RemotableViewMethod + public void showPrevious() { + // if the flipper is currently flipping automatically, and showPrevious() is called + // we should we should make sure to reset the timer + if (mRunning) { + mHandler.removeMessages(FLIP_MSG); + Message msg = mHandler.obtainMessage(FLIP_MSG); + mHandler.sendMessageDelayed(msg, mFlipInterval); + } + super.showPrevious(); + } + + /** + + /** * Internal method to start or stop dispatching flip {@link Message} based * on {@link #mRunning} and {@link #mVisible} state. */ @@ -229,8 +263,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator { if (msg.what == FLIP_MSG) { if (mRunning) { showNext(); - msg = obtainMessage(FLIP_MSG); - sendMessageDelayed(msg, mFlipInterval); } } } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 50745dc0..f23a723 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -266,6 +266,63 @@ public class RemoteViews implements Parcelable, Filter { public final static int TAG = 3; } + private class ReflectionActionWithoutParams extends Action { + int viewId; + String methodName; + + public final static int TAG = 5; + + ReflectionActionWithoutParams(int viewId, String methodName) { + this.viewId = viewId; + this.methodName = methodName; + } + + ReflectionActionWithoutParams(Parcel in) { + this.viewId = in.readInt(); + this.methodName = in.readString(); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(TAG); + out.writeInt(this.viewId); + out.writeString(this.methodName); + } + + @Override + public void apply(View root) { + final View view = root.findViewById(viewId); + if (view == null) { + throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId)); + } + + Class klass = view.getClass(); + Method method; + try { + method = klass.getMethod(this.methodName); + } catch (NoSuchMethodException ex) { + throw new ActionException("view: " + klass.getName() + " doesn't have method: " + + this.methodName + "()"); + } + + if (!method.isAnnotationPresent(RemotableViewMethod.class)) { + throw new ActionException("view: " + klass.getName() + + " can't use method with RemoteViews: " + + this.methodName + "()"); + } + + try { + //noinspection ConstantIfStatement + if (false) { + Log.d("RemoteViews", "view: " + klass.getName() + " calling method: " + + this.methodName + "()"); + } + method.invoke(view); + } catch (Exception ex) { + throw new ActionException(ex); + } + } + } + /** * Base class for the reflection actions. */ @@ -571,6 +628,9 @@ public class RemoteViews implements Parcelable, Filter { case ViewGroupAction.TAG: mActions.add(new ViewGroupAction(parcel)); break; + case ReflectionActionWithoutParams.TAG: + mActions.add(new ReflectionActionWithoutParams(parcel)); + break; default: throw new ActionException("Tag " + tag + " not found"); } @@ -632,6 +692,24 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link AdapterViewFlipper#showNext()} + * + * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showNext()} + */ + public void showNext(int viewId) { + addAction(new ReflectionActionWithoutParams(viewId, "showNext")); + } + + /** + * Equivalent to calling {@link AdapterViewFlipper#showPrevious()} + * + * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showPrevious()} + */ + public void showPrevious(int viewId) { + addAction(new ReflectionActionWithoutParams(viewId, "showPrevious")); + } + + /** * Equivalent to calling View.setVisibility * * @param viewId The id of the view whose visibility should change diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index ebf5d6e..cd1e422 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -202,6 +202,7 @@ public class RemoteViewsAdapter extends BaseAdapter { int count; int viewTypeCount; boolean hasStableIds; + boolean isDataDirty; Map<Integer, Integer> mTypeIdIndexMap; RemoteViewsInfo() { @@ -209,6 +210,7 @@ public class RemoteViewsAdapter extends BaseAdapter { // by default there is at least one dummy view type viewTypeCount = 1; hasStableIds = true; + isDataDirty = false; mTypeIdIndexMap = new HashMap<Integer, Integer>(); } } @@ -282,6 +284,39 @@ public class RemoteViewsAdapter extends BaseAdapter { } } + protected void onNotifyDataSetChanged() { + // we mark the data as dirty so that the next call to fetch views will result in + // an onDataSetDirty() call from the adapter + synchronized (mViewCacheInfo) { + mViewCacheInfo.isDataDirty = true; + } + } + + private void updateNotifyDataSetChanged() { + // actually calls through to the factory to notify it to update + if (mServiceConnection.isConnected()) { + IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory(); + try { + factory.onDataSetChanged(); + } catch (RemoteException e) { + e.printStackTrace(); + } + + } + + // re-request the new metadata (only after the notification to the factory) + requestMetaData(); + + // post a new runnable on the main thread to propagate the notification back + // to the base adapter + mMainQueue.post(new Runnable() { + @Override + public void run() { + completeNotifyDataSetChanged(); + } + }); + } + protected void updateRemoteViewsInfo(int position) { if (mServiceConnection.isConnected()) { IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory(); @@ -499,6 +534,16 @@ public class RemoteViewsAdapter extends BaseAdapter { @Override public void run() { while (mBackgroundLoaderEnabled) { + // notify the RemoteViews factory if necessary + boolean isDataDirty = false; + synchronized (mViewCacheInfo) { + isDataDirty = mViewCacheInfo.isDataDirty; + mViewCacheInfo.isDataDirty = false; + } + if (isDataDirty) { + updateNotifyDataSetChanged(); + } + int index = -1; synchronized (mViewCacheLoadIndices) { if (!mViewCacheLoadIndices.isEmpty()) { @@ -668,6 +713,12 @@ public class RemoteViewsAdapter extends BaseAdapter { public void notifyDataSetChanged() { // flush the cache so that we can reload new items from the service mViewCache.flushCache(); + + // notify the factory that it's data may no longer be valid + mViewCache.onNotifyDataSetChanged(); + } + + public void completeNotifyDataSetChanged() { super.notifyDataSetChanged(); } diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index 7c9d7ff..584fa25 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -43,9 +43,27 @@ public abstract class RemoteViewsService extends Service { * An interface for an adapter between a remote collection view (ListView, GridView, etc) and * the underlying data for that view. The implementor is responsible for making a RemoteView * for each item in the data set. + * + * @see android.widget.Adapter + * @see android.appwidget.AppWidgetManager */ public interface RemoteViewsFactory { + /** + * Called when your factory is first constructed. The same factory may be shared across + * multiple RemoteViewAdapters depending on the intent passed. + */ public void onCreate(); + /** + * Called when notifyDataSetChanged() is triggered on the remote adapter. This allows a + * RemoteViewsFactory to respond to data changes by updating any internal references. + * + * @see android.appwidget.AppWidgetManager#notifyAppWidgetViewDataChanged(int[], int) + */ + public void onDataSetChanged(); + /** + * Called when the last RemoteViewsAdapter that is associated with this factory is + * unbound. + */ public void onDestroy(); public int getCount(); @@ -64,7 +82,9 @@ public abstract class RemoteViewsService extends Service { public RemoteViewsFactoryAdapter(RemoteViewsFactory factory) { mFactory = factory; } - + public void onDataSetChanged() { + mFactory.onDataSetChanged(); + } public int getCount() { return mFactory.getCount(); } diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java index 3b113ae..93a1179 100644 --- a/core/java/android/widget/ScrollBarDrawable.java +++ b/core/java/android/widget/ScrollBarDrawable.java @@ -111,8 +111,7 @@ public class ScrollBarDrawable extends Drawable { } Rect r = getBounds(); - if (canvas.quickReject(r.left, r.top, r.right, r.bottom, - Canvas.EdgeType.AA)) { + if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) { return; } if (drawTrack) { diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index e3aca6a..5797cbb 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -34,6 +34,7 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.animation.LinearInterpolator; import android.widget.RemoteViews.RemoteView; @RemoteView @@ -47,22 +48,22 @@ public class StackView extends AdapterViewAnimator { /** * Default animation parameters */ - private final int DEFAULT_ANIMATION_DURATION = 400; + private final int DEFAULT_ANIMATION_DURATION = 500; private final int MINIMUM_ANIMATION_DURATION = 50; /** * These specify the different gesture states */ - private final int GESTURE_NONE = 0; - private final int GESTURE_SLIDE_UP = 1; - private final int GESTURE_SLIDE_DOWN = 2; + private static final int GESTURE_NONE = 0; + private static final int GESTURE_SLIDE_UP = 1; + private static final int GESTURE_SLIDE_DOWN = 2; /** * Specifies how far you need to swipe (up or down) before it * will be consider a completed gesture when you lift your finger */ - private final float SWIPE_THRESHOLD_RATIO = 0.35f; - private final float SLIDE_UP_RATIO = 0.7f; + private static final float SWIPE_THRESHOLD_RATIO = 0.35f; + private static final float SLIDE_UP_RATIO = 0.7f; private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>(); private final WeakHashMap<View, Integer> @@ -93,9 +94,6 @@ public class StackView extends AdapterViewAnimator { private StackSlider mStackSlider; private boolean mFirstLayoutHappened = false; - // TODO: temp hack to get this thing started - int mIndex = 5; - public StackView(Context context) { super(context); initStackView(); @@ -107,10 +105,10 @@ public class StackView extends AdapterViewAnimator { } private void initStackView() { - configureViewAnimator(4, 2); + configureViewAnimator(4, 2, false); setStaticTransformationsEnabled(true); final ViewConfiguration configuration = ViewConfiguration.get(getContext()); - mTouchSlop = configuration.getScaledTouchSlop();// + 5; + mTouchSlop = configuration.getScaledTouchSlop(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mActivePointerId = INVALID_POINTER; @@ -120,7 +118,7 @@ public class StackView extends AdapterViewAnimator { mStackSlider = new StackSlider(); if (!sPaintsInitialized) { - initializePaints(getContext()); + initializePaints(); } } @@ -133,6 +131,8 @@ public class StackView extends AdapterViewAnimator { if (view.getAlpha() == 1) { view.setAlpha(0); } + view.setVisibility(VISIBLE); + PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, view, "alpha", view.getAlpha(), 1.0f); fadeIn.start(); @@ -142,7 +142,7 @@ public class StackView extends AdapterViewAnimator { LayoutParams lp = (LayoutParams) view.getLayoutParams(); - int largestDuration = (int) Math.round( + int largestDuration = Math.round( (lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION); int duration = largestDuration; if (mYVelocity != 0) { @@ -152,18 +152,20 @@ public class StackView extends AdapterViewAnimator { duration = Math.min(duration, largestDuration); duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); - PropertyAnimator slideInY = new PropertyAnimator(duration, mStackSlider, + StackSlider animationSlider = new StackSlider(mStackSlider); + PropertyAnimator slideInY = new PropertyAnimator(duration, animationSlider, "YProgress", mStackSlider.getYProgress(), 0); + slideInY.setInterpolator(new LinearInterpolator()); slideInY.start(); - PropertyAnimator slideInX = new PropertyAnimator(duration, mStackSlider, + PropertyAnimator slideInX = new PropertyAnimator(duration, animationSlider, "XProgress", mStackSlider.getXProgress(), 0); + slideInX.setInterpolator(new LinearInterpolator()); slideInX.start(); - } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) { // Slide item out LayoutParams lp = (LayoutParams) view.getLayoutParams(); - int largestDuration = (int) Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION); + int largestDuration = Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION); int duration = largestDuration; if (mYVelocity != 0) { duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity); @@ -172,16 +174,21 @@ public class StackView extends AdapterViewAnimator { duration = Math.min(duration, largestDuration); duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); - PropertyAnimator slideOutY = new PropertyAnimator(duration, mStackSlider, + StackSlider animationSlider = new StackSlider(mStackSlider); + PropertyAnimator slideOutY = new PropertyAnimator(duration, animationSlider, "YProgress", mStackSlider.getYProgress(), 1); + slideOutY.setInterpolator(new LinearInterpolator()); slideOutY.start(); - PropertyAnimator slideOutX = new PropertyAnimator(duration, mStackSlider, + PropertyAnimator slideOutX = new PropertyAnimator(duration, animationSlider, "XProgress", mStackSlider.getXProgress(), 0); + slideOutX.setInterpolator(new LinearInterpolator()); slideOutX.start(); - } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) { // Make sure this view that is "waiting in the wings" is invisible view.setAlpha(0.0f); + view.setVisibility(INVISIBLE); + LayoutParams lp = (LayoutParams) view.getLayoutParams(); + lp.setVerticalOffset(-mViewHeight); } else if (toIndex == -1) { // Fade item out PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, @@ -194,13 +201,9 @@ public class StackView extends AdapterViewAnimator { * Apply any necessary tranforms for the child that is being added. */ void applyTransformForChildAtIndex(View child, int relativeIndex) { - float rotation; - if (!mRotations.containsKey(child)) { - rotation = (float) (Math.random()*26 - 13); + float rotation = (float) (Math.random()*26 - 13); mRotations.put(child, rotation); - } else { - rotation = mRotations.get(child); } // Child has been removed @@ -235,8 +238,8 @@ public class StackView extends AdapterViewAnimator { } if (!mFirstLayoutHappened) { - mViewHeight = (int) Math.round(SLIDE_UP_RATIO*getMeasuredHeight()); - mSwipeThreshold = (int) Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight); + mViewHeight = Math.round(SLIDE_UP_RATIO*getMeasuredHeight()); + mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight); // TODO: Right now this walks all the way up the view hierarchy and disables // ClipChildren and ClipToPadding. We're probably going to want to reset @@ -391,7 +394,7 @@ public class StackView extends AdapterViewAnimator { float y = ev.getY(index); touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); - if (touchRect.contains((int) Math.round(x), (int) Math.round(y))) { + if (touchRect.contains(Math.round(x), Math.round(y))) { float oldX = ev.getX(activePointerIndex); float oldY = ev.getY(activePointerIndex); @@ -437,23 +440,29 @@ public class StackView extends AdapterViewAnimator { mHighlight.bringToFront(); } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { // Didn't swipe up far enough, snap back down - int duration = (int) Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION); + int duration = Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION); - PropertyAnimator snapBackY = new PropertyAnimator(duration, mStackSlider, + StackSlider animationSlider = new StackSlider(mStackSlider); + PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider, "YProgress", mStackSlider.getYProgress(), 0); + snapBackY.setInterpolator(new LinearInterpolator()); snapBackY.start(); - PropertyAnimator snapBackX = new PropertyAnimator(duration, mStackSlider, + PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider, "XProgress", mStackSlider.getXProgress(), 0); + snapBackX.setInterpolator(new LinearInterpolator()); snapBackX.start(); } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { // Didn't swipe down far enough, snap back up - int duration = (int) Math.round((1 - + int duration = Math.round((1 - mStackSlider.getYProgress())*DEFAULT_ANIMATION_DURATION); - PropertyAnimator snapBackY = new PropertyAnimator(duration, mStackSlider, + StackSlider animationSlider = new StackSlider(mStackSlider); + PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider, "YProgress", mStackSlider.getYProgress(), 1); + snapBackY.setInterpolator(new LinearInterpolator()); snapBackY.start(); - PropertyAnimator snapBackX = new PropertyAnimator(duration, mStackSlider, + PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider, "XProgress", mStackSlider.getXProgress(), 0); + snapBackX.setInterpolator(new LinearInterpolator()); snapBackX.start(); } @@ -466,6 +475,15 @@ public class StackView extends AdapterViewAnimator { float mYProgress; float mXProgress; + public StackSlider() { + } + + public StackSlider(StackSlider copy) { + mView = copy.mView; + mYProgress = copy.mYProgress; + mXProgress = copy.mXProgress; + } + private float cubic(float r) { return (float) (Math.pow(2*r-1, 3) + 1)/2.0f; } @@ -488,6 +506,15 @@ public class StackView extends AdapterViewAnimator { } } + private float rotationInterpolator(float r) { + float pivot = 0.2f; + if (r < pivot) { + return 0; + } else { + return (r-pivot)/(1-pivot); + } + } + void setView(View v) { mView = v; } @@ -502,10 +529,23 @@ public class StackView extends AdapterViewAnimator { final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); - viewLp.setVerticalOffset((int) Math.round(-r*mViewHeight)); - highlightLp.setVerticalOffset((int) Math.round(-r*mViewHeight)); + viewLp.setVerticalOffset(Math.round(-r*mViewHeight)); + highlightLp.setVerticalOffset(Math.round(-r*mViewHeight)); mHighlight.setAlpha(highlightAlphaInterpolator(r)); + + float alpha = viewAlphaInterpolator(1-r); + + // We make sure that views which can't be seen (have 0 alpha) are also invisible + // so that they don't interfere with click events. + if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) { + mView.setVisibility(VISIBLE); + } else if (alpha == 0 && mView.getAlpha() != 0 && mView.getVisibility() == VISIBLE) { + mView.setVisibility(INVISIBLE); + } + mView.setAlpha(viewAlphaInterpolator(1-r)); + mView.setRotationX(90.0f*rotationInterpolator(r)); + mHighlight.setRotationX(90.0f*rotationInterpolator(r)); } public void setXProgress(float r) { @@ -518,8 +558,8 @@ public class StackView extends AdapterViewAnimator { final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); - viewLp.setHorizontalOffset((int) Math.round(r*mViewHeight)); - highlightLp.setHorizontalOffset((int) Math.round(r*mViewHeight)); + viewLp.setHorizontalOffset(Math.round(r*mViewHeight)); + highlightLp.setHorizontalOffset(Math.round(r*mViewHeight)); } float getYProgress() { @@ -534,7 +574,7 @@ public class StackView extends AdapterViewAnimator { @Override public void onRemoteAdapterConnected() { super.onRemoteAdapterConnected(); - setDisplayedChild(mIndex); + setDisplayedChild(mWhichChild); } private static final Paint sHolographicPaint = new Paint(); @@ -542,7 +582,7 @@ public class StackView extends AdapterViewAnimator { private static boolean sPaintsInitialized = false; private static final float STROKE_WIDTH = 3.0f; - static void initializePaints(Context context) { + static void initializePaints() { sHolographicPaint.setColor(0xff6699ff); sHolographicPaint.setFilterBitmap(true); sErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); @@ -551,22 +591,29 @@ public class StackView extends AdapterViewAnimator { } static Bitmap createOutline(View v) { + if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) { + return null; + } + Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); + float rotationX = v.getRotationX(); + v.setRotationX(0); canvas.concat(v.getMatrix()); v.draw(canvas); + v.setRotationX(rotationX); Bitmap outlineBitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888); Canvas outlineCanvas = new Canvas(outlineBitmap); - drawOutline(outlineCanvas, v.getMeasuredWidth(), v.getMeasuredHeight(), bitmap); + drawOutline(outlineCanvas, bitmap); bitmap.recycle(); return outlineBitmap; } - static void drawOutline(Canvas dest, int destWidth, int destHeight, Bitmap src) { + static void drawOutline(Canvas dest, Bitmap src) { dest.drawColor(0, PorterDuff.Mode.CLEAR); Bitmap mask = src.extractAlpha(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index b413aee..825de25 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -21,9 +21,9 @@ import com.android.internal.widget.EditableInputConnection; import org.xmlpull.v1.XmlPullParserException; +import android.content.ClipboardManager; import android.content.ClippedData; import android.content.Context; -import android.content.ClipboardManager; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -4410,6 +4410,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); } + hideControllers(); + switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: /* @@ -6263,6 +6265,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(buffer, start, before, after); onTextChanged(buffer, start, before, after); + hideControllers(); } /** @@ -6747,11 +6750,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void prepareCursorControllers() { - boolean atLeastOneController = false; - // TODO Add an extra android:cursorController flag to disable the controller? if (mCursorVisible && mLayout != null) { - atLeastOneController = true; if (mInsertionPointCursorController == null) { mInsertionPointCursorController = new InsertionPointCursorController(); } @@ -6760,7 +6760,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (canSelectText() && mLayout != null) { - atLeastOneController = true; if (mSelectionModifierCursorController == null) { mSelectionModifierCursorController = new SelectionModifierCursorController(); } @@ -6769,12 +6768,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Stop selection mode if the controller becomes unavailable. finishSelectionActionMode(); } - - if (atLeastOneController) { - Resources res = mContext.getResources(); - mCursorControllerVerticalOffset = res.getDimensionPixelOffset( - com.android.internal.R.dimen.cursor_controller_vertical_offset); - } } /** @@ -7620,6 +7613,75 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void draw(Canvas canvas); } + private class Handle { + Drawable mDrawable; + // Vertical extension of the touch region + int mTopExtension, mBottomExtension; + // Position of the virtual finger position on screen + int mHopSpotVertcalPosition; + + Handle(Drawable drawable) { + mDrawable = drawable; + } + + void positionAtCursor(final int offset, boolean bottom) { + final int drawableWidth = mDrawable.getIntrinsicWidth(); + final int drawableHeight = mDrawable.getIntrinsicHeight(); + final int line = mLayout.getLineForOffset(offset); + final int lineTop = mLayout.getLineTop(line); + final int lineBottom = mLayout.getLineBottom(line); + + mHopSpotVertcalPosition = lineTop + (bottom ? (3 * (lineBottom - lineTop)) / 4 : + (lineBottom - lineTop) / 4); + + final Rect bounds = sCursorControllerTempRect; + bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0); + bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2; + + mTopExtension = bottom ? 0 : drawableHeight / 2; + mBottomExtension = drawableHeight; + + // Extend touch region up when editing the last line of text (or a single line) so that + // it is easier to grab. + if (line == mLayout.getLineCount() - 1) { + mTopExtension = (lineBottom - lineTop) - drawableHeight / 2; + } + + bounds.right = bounds.left + drawableWidth; + bounds.bottom = bounds.top + drawableHeight; + + int boundTopBefore = bounds.top; + convertFromViewportToContentCoordinates(bounds); + mHopSpotVertcalPosition += bounds.top - boundTopBefore; + mDrawable.setBounds(bounds); + postInvalidate(); + } + + boolean hasFingerOn(float x, float y) { + // Simulate a 'fat finger' to ease grabbing of the controller. + // Expands according to controller image size instead of using dip distance. + // Assumes controller imager has a sensible size, proportionnal to screen density. + final int drawableWidth = mDrawable.getIntrinsicWidth(); + final Rect fingerRect = sCursorControllerTempRect; + fingerRect.set((int) (x - drawableWidth / 2.0), + (int) (y - mBottomExtension), + (int) (x + drawableWidth / 2.0), + (int) (y + mTopExtension)); + return Rect.intersects(mDrawable.getBounds(), fingerRect); + } + + void postInvalidate() { + final Rect bounds = mDrawable.getBounds(); + TextView.this.postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + void postInvalidateDelayed(long delay) { + final Rect bounds = mDrawable.getBounds(); + TextView.this.postInvalidateDelayed(delay, bounds.left, bounds.top, + bounds.right, bounds.bottom); + } + } + class InsertionPointCursorController implements CursorController { private static final int DELAY_BEFORE_FADE_OUT = 2100; @@ -7628,7 +7690,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Starting time of the fade timer private long mFadeOutTimerStart; // The cursor controller image - private final Drawable mDrawable; + private final Handle mHandle; // Used to detect a tap (vs drag) on the controller private long mOnDownTimerStart; // Offset between finger hot point on cursor controller and actual cursor @@ -7636,7 +7698,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener InsertionPointCursorController() { Resources res = mContext.getResources(); - mDrawable = res.getDrawable(com.android.internal.R.drawable.cursor_controller); + mHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); } public void show() { @@ -7652,7 +7714,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Start fading out, only if not already in progress if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) { mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT; - postInvalidate(mDrawable); + mHandle.postInvalidate(); } } } @@ -7661,19 +7723,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mIsVisible) { int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart); if (time <= DELAY_BEFORE_FADE_OUT) { - postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time, mDrawable); + mHandle.postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time); } else { time -= DELAY_BEFORE_FADE_OUT; if (time <= FADE_OUT_DURATION) { - final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION; - mDrawable.setAlpha(alpha); - postInvalidateDelayed(30, mDrawable); + final int alpha = (int) + ((255.0 * (FADE_OUT_DURATION - time)) / FADE_OUT_DURATION); + mHandle.mDrawable.setAlpha(alpha); + mHandle.postInvalidateDelayed(30); } else { - mDrawable.setAlpha(0); + mHandle.mDrawable.setAlpha(0); mIsVisible = false; } } - mDrawable.draw(canvas); + mHandle.mDrawable.draw(canvas); } } @@ -7688,7 +7751,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void updateDrawablePosition() { if (mIsVisible) { // Clear previous cursor controller before bounds are updated - postInvalidate(mDrawable); + mHandle.postInvalidate(); } final int offset = getSelectionStart(); @@ -7700,10 +7763,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return; } - positionDrawableUnderCursor(offset, mDrawable); + mHandle.positionAtCursor(offset, true); mFadeOutTimerStart = System.currentTimeMillis(); - mDrawable.setAlpha(255); + mHandle.mDrawable.setAlpha(255); } public void onTouchEvent(MotionEvent event) { @@ -7713,7 +7776,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final float x = event.getX(); final float y = event.getY(); - if (fingerIsOnDrawable(x, y, mDrawable)) { + if (mHandle.hasFingerOn(x, y)) { show(); if (mMovement instanceof ArrowKeyMovementMethod) { @@ -7726,9 +7789,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mParent.requestDisallowInterceptTouchEvent(true); } - final Rect bounds = mDrawable.getBounds(); + final Rect bounds = mHandle.mDrawable.getBounds(); mOffsetX = (bounds.left + bounds.right) / 2.0f - x; - mOffsetY = bounds.top - mCursorControllerVerticalOffset - y; + mOffsetY = mHandle.mHopSpotVertcalPosition - y; mOnDownTimerStart = event.getEventTime(); } @@ -7769,7 +7832,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Starting time of the fade timer private long mFadeOutTimerStart; // The cursor controller images - private final Drawable mStartDrawable, mEndDrawable; + private final Handle mStartHandle, mEndHandle; // Offset between finger hot point on active cursor controller and actual cursor private float mOffsetX, mOffsetY; // The offsets of that last touch down event. Remembered to start selection there. @@ -7777,8 +7840,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener SelectionModifierCursorController() { Resources res = mContext.getResources(); - mStartDrawable = res.getDrawable(com.android.internal.R.drawable.selection_start_handle); - mEndDrawable = res.getDrawable(com.android.internal.R.drawable.selection_end_handle); + mStartHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); + mEndHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); } public void show() { @@ -7793,15 +7856,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void hide() { if (mIsVisible && (mFadeOutTimerStart < 0)) { mFadeOutTimerStart = System.currentTimeMillis(); - postInvalidate(mStartDrawable); - postInvalidate(mEndDrawable); + mStartHandle.postInvalidate(); + mEndHandle.postInvalidate(); } } public void cancelFadeOutAnimation() { mIsVisible = false; - postInvalidate(mStartDrawable); - postInvalidate(mEndDrawable); + mStartHandle.postInvalidate(); + mEndHandle.postInvalidate(); } public void draw(Canvas canvas) { @@ -7810,18 +7873,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart); if (time <= FADE_OUT_DURATION) { final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION; - mStartDrawable.setAlpha(alpha); - mEndDrawable.setAlpha(alpha); - postInvalidateDelayed(30, mStartDrawable); - postInvalidateDelayed(30, mEndDrawable); + mStartHandle.mDrawable.setAlpha(alpha); + mEndHandle.mDrawable.setAlpha(alpha); + mStartHandle.postInvalidateDelayed(30); + mEndHandle.postInvalidateDelayed(30); } else { - mStartDrawable.setAlpha(0); - mEndDrawable.setAlpha(0); + mStartHandle.mDrawable.setAlpha(0); + mEndHandle.mDrawable.setAlpha(0); mIsVisible = false; } } - mStartDrawable.draw(canvas); - mEndDrawable.draw(canvas); + mStartHandle.mDrawable.draw(canvas); + mEndHandle.mDrawable.draw(canvas); } } @@ -7861,8 +7924,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void updateDrawablesPositions() { if (mIsVisible) { // Clear previous cursor controller before bounds are updated - postInvalidate(mStartDrawable); - postInvalidate(mEndDrawable); + mStartHandle.postInvalidate(); + mEndHandle.postInvalidate(); } final int selectionStart = getSelectionStart(); @@ -7875,11 +7938,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return; } - positionDrawableUnderCursor(selectionStart, mStartDrawable); - positionDrawableUnderCursor(selectionEnd, mEndDrawable); + boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) == mLayout.getLineForOffset(selectionEnd); + mStartHandle.positionAtCursor(selectionStart, oneLineSelection); + mEndHandle.positionAtCursor(selectionEnd, true); - mStartDrawable.setAlpha(255); - mEndDrawable.setAlpha(255); + mStartHandle.mDrawable.setAlpha(255); + mEndHandle.mDrawable.setAlpha(255); } public void onTouchEvent(MotionEvent event) { @@ -7889,26 +7953,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int x = (int) event.getX(); final int y = (int) event.getY(); - // Remember finger down position, to be able to start selection on that point + // Remember finger down position, to be able to start selection from there mMinTouchOffset = mMaxTouchOffset = getOffset(x, y); if (mIsVisible) { if (mMovement instanceof ArrowKeyMovementMethod) { - boolean isOnStart = fingerIsOnDrawable(x, y, mStartDrawable); - boolean isOnEnd = fingerIsOnDrawable(x, y, mEndDrawable); + boolean isOnStart = mStartHandle.hasFingerOn(x, y); + boolean isOnEnd = mEndHandle.hasFingerOn(x, y); if (isOnStart || isOnEnd) { if (mParent != null) { - // Prevent possible scrollView parent from scrolling, so that - // we can use auto-scrolling. + // Prevent possible scrollView parent from scrolling, so + // that we can use auto-scrolling. mParent.requestDisallowInterceptTouchEvent(true); } - // Start handle will be dragged in case BOTH controller are under finger - mStartIsDragged = isOnStart; - final Rect bounds = - (mStartIsDragged ? mStartDrawable : mEndDrawable).getBounds(); + // In case both controllers are under finger (very small + // selection region), arbitrarily pick end controller. + mStartIsDragged = !isOnEnd; + final Handle draggedHandle = mStartIsDragged ? mStartHandle : mEndHandle; + final Rect bounds = draggedHandle.mDrawable.getBounds(); mOffsetX = (bounds.left + bounds.right) / 2.0f - x; - mOffsetY = bounds.top - mCursorControllerVerticalOffset - y; + mOffsetY = draggedHandle.mHopSpotVertcalPosition - y; ((ArrowKeyMovementMethod)mMovement).setCursorController(this); } @@ -7967,61 +8032,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - // Helper methods used by CursorController implementations - - private void positionDrawableUnderCursor(final int offset, Drawable drawable) { - final int drawableWidth = drawable.getIntrinsicWidth(); - final int drawableHeight = drawable.getIntrinsicHeight(); - final int line = mLayout.getLineForOffset(offset); - - final Rect bounds = sCursorControllerTempRect; - bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5 - drawableWidth / 2.0); - bounds.top = mLayout.getLineTop(line + 1); - - // Move cursor controller a little bit up when editing the last line of text - // (or a single line) so that it is visible and easier to grab. - if (line == mLayout.getLineCount() - 1) { - bounds.top -= Math.max(0, drawableHeight / 2 - getExtendedPaddingBottom()); - } - - bounds.right = bounds.left + drawableWidth; - bounds.bottom = bounds.top + drawableHeight; - - convertFromViewportToContentCoordinates(bounds); - drawable.setBounds(bounds); - postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); - } - - private boolean fingerIsOnDrawable(float x, float y, Drawable drawable) { - // Simulate a 'fat finger' to ease grabbing of the controller. - // Expands according to controller image size instead of using density. - // Assumes controller imager has a sensible size, proportionnal to density. - final int drawableWidth = drawable.getIntrinsicWidth(); - final int drawableHeight = drawable.getIntrinsicHeight(); - final Rect fingerRect = sCursorControllerTempRect; - fingerRect.set((int) (x - drawableWidth / 2.0), - (int) (y - drawableHeight), - (int) (x + drawableWidth / 2.0), - (int) y); - return Rect.intersects(drawable.getBounds(), fingerRect); - } - - private void postInvalidate(Drawable drawable) { - final Rect bounds = drawable.getBounds(); - postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); - } - - private void postInvalidateDelayed(long delay, Drawable drawable) { - final Rect bounds = drawable.getBounds(); - postInvalidateDelayed(delay, bounds.left, bounds.top, bounds.right, bounds.bottom); - } - private void hideInsertionPointCursorController() { if (mInsertionPointCursorController != null) { mInsertionPointCursorController.hide(); } } + private void hideControllers() { + hideInsertionPointCursorController(); + finishSelectionActionMode(); + } + /** * Get the offset character closest to the specified absolute position. * @@ -8083,7 +8104,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final TextPaint mTextPaint; private boolean mUserSetTextScaleX; private final Paint mHighlightPaint; - private int mHighlightColor = 0xD077A14B; + private int mHighlightColor = 0xCC475925; private Layout mLayout; private long mShowCursor; @@ -8093,8 +8114,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Cursor Controllers. Null when disabled. private CursorController mInsertionPointCursorController; private CursorController mSelectionModifierCursorController; - // Stored once and for all. - private int mCursorControllerVerticalOffset; private boolean mShouldStartSelectionActionMode = false; private ActionMode mSelectionActionMode; // Created once and shared by different CursorController helper methods. diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 75084db..ef0ab41 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -429,7 +429,8 @@ public class ActionBarImpl extends ActionBar { public ActionModeImpl(ActionMode.Callback callback) { mCallback = callback; - mMenu = new MenuBuilder(mActionView.getContext()); + mMenu = new MenuBuilder(mActionView.getContext()) + .setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); mMenu.setCallback(this); } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index d040d3f..1620778 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -22,8 +22,8 @@ import android.telephony.SignalStrength; interface IBatteryStats { byte[] getStatistics(); - void noteStartWakelock(int uid, String name, int type); - void noteStopWakelock(int uid, String name, int type); + void noteStartWakelock(int uid, int pid, String name, int type); + void noteStopWakelock(int uid, int pid, String name, int type); /* DO NOT CHANGE the position of noteStartSensor without updating SensorService.cpp */ diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index ce5959d..e1c5564 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -18,17 +18,31 @@ package com.android.internal.app; import android.app.Activity; import android.os.Bundle; +import android.view.MotionEvent; import android.widget.ImageView; +import android.widget.Toast; public class PlatLogoActivity extends Activity { + Toast mToast; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mToast = Toast.makeText(this, "Zombie art by Jack Larson", Toast.LENGTH_SHORT); + ImageView content = new ImageView(this); content.setImageResource(com.android.internal.R.drawable.platlogo); content.setScaleType(ImageView.ScaleType.FIT_CENTER); setContentView(content); } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + mToast.show(); + } + return super.dispatchTouchEvent(ev); + } } diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl index f0920d1..216d985 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl @@ -24,6 +24,6 @@ import android.widget.RemoteViews; oneway interface IAppWidgetHost { void updateAppWidget(int appWidgetId, in RemoteViews views); void providerChanged(int appWidgetId, in AppWidgetProviderInfo info); - void viewDataChanged(int appWidgetId, in RemoteViews views, int viewId); + void viewDataChanged(int appWidgetId, int viewId); } diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index af75d5b..4d56745 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -40,8 +40,9 @@ interface IAppWidgetService { // for AppWidgetManager // void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views); + void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views); void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views); - void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, in RemoteViews views, int viewId); + void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, int viewId); List<AppWidgetProviderInfo> getInstalledProviders(); AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId); void bindAppWidgetId(int appWidgetId, in ComponentName provider); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 13b3033..a70dbf6 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -63,7 +63,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 49; + private static final int VERSION = 50; // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 1000; @@ -107,6 +107,7 @@ public final class BatteryStatsImpl extends BatteryStats { int mNumHistoryItems; HistoryItem mHistory; HistoryItem mHistoryEnd; + HistoryItem mHistoryLastEnd; HistoryItem mHistoryCache; final HistoryItem mHistoryCur = new HistoryItem(); @@ -451,7 +452,7 @@ public final class BatteryStatsImpl extends BatteryStats { * Clear state of this timer. Returns true if the timer is inactive * so can be completely dropped. */ - boolean reset(boolean detachIfReset) { + boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { mTotalTime = mLoadedTime = mLastTime = 0; mCount = mLoadedCount = mLastCount = 0; if (detachIfReset) { @@ -713,8 +714,8 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mTrackingReportedValues ? 1 : 0); } - boolean reset(boolean detachIfReset) { - super.reset(detachIfReset); + boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + super.reset(stats, detachIfReset); setStale(); return true; } @@ -749,7 +750,7 @@ public final class BatteryStatsImpl extends BatteryStats { long mUpdateTime; /** - * The total time at which the timer was acquired, to determine if + * The total time at which the timer was acquired, to determine if it * was actually held for an interesting duration. */ long mAcquireTime; @@ -890,9 +891,14 @@ public final class BatteryStatsImpl extends BatteryStats { return mCount; } - boolean reset(boolean detachIfReset) { + boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { boolean canDetach = mNesting <= 0; - super.reset(canDetach && detachIfReset); + super.reset(stats, canDetach && detachIfReset); + if (mNesting > 0) { + mUpdateTime = stats.getBatteryRealtimeLocked( + SystemClock.elapsedRealtime() * 1000); + } + mAcquireTime = mTotalTime; return canDetach; } @@ -1115,6 +1121,26 @@ public final class BatteryStatsImpl extends BatteryStats { if (!mHaveBatteryLevel || !mRecordingHistory) { return; } + + // If the current time is basically the same as the last time, + // just collapse into one record. + if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE + && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+100)) { + // If the current is the same as the one before, then we no + // longer need the entry. + if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE + && mHistoryLastEnd.same(mHistoryCur)) { + mHistoryLastEnd.next = null; + mHistoryEnd.next = mHistoryCache; + mHistoryCache = mHistoryEnd; + mHistoryEnd = mHistoryLastEnd; + mHistoryLastEnd = null; + } else { + mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, mHistoryCur); + } + return; + } + if (mNumHistoryItems >= MAX_HISTORY_ITEMS) { // Once we've reached the maximum number of items, we only // record changes to the battery level. @@ -1123,6 +1149,7 @@ public final class BatteryStatsImpl extends BatteryStats { return; } } + addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE); } @@ -1141,6 +1168,7 @@ public final class BatteryStatsImpl extends BatteryStats { void addHistoryRecordLocked(HistoryItem rec) { mNumHistoryItems++; rec.next = null; + mHistoryLastEnd = mHistoryEnd; if (mHistoryEnd != null) { mHistoryEnd.next = rec; mHistoryEnd = rec; @@ -1153,7 +1181,7 @@ public final class BatteryStatsImpl extends BatteryStats { if (mHistory != null) { mHistoryEnd.next = mHistoryCache; mHistoryCache = mHistory; - mHistory = mHistoryEnd = null; + mHistory = mHistoryLastEnd = mHistoryEnd = null; } mNumHistoryItems = 0; mHistoryBaseTime = 0; @@ -1211,6 +1239,89 @@ public final class BatteryStatsImpl extends BatteryStats { mBluetoothPingStart = -1; } + int mWakeLockNesting; + + public void noteStartWakeLocked(int uid, int pid, String name, int type) { + if (type == WAKE_TYPE_PARTIAL) { + // Only care about partial wake locks, since full wake locks + // will be canceled when the user puts the screen to sleep. + if (mWakeLockNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + mWakeLockNesting++; + } + if (uid >= 0) { + getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type); + } + } + + public void noteStopWakeLocked(int uid, int pid, String name, int type) { + if (type == WAKE_TYPE_PARTIAL) { + mWakeLockNesting--; + if (mWakeLockNesting == 0) { + mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + } + if (uid >= 0) { + getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type); + } + } + + public void noteProcessDiedLocked(int uid, int pid) { + Uid u = mUidStats.get(uid); + if (u != null) { + u.mPids.remove(pid); + } + } + + public long getProcessWakeTime(int uid, int pid, long realtime) { + Uid u = mUidStats.get(uid); + if (u != null) { + Uid.Pid p = u.mPids.get(pid); + if (p != null) { + return p.mWakeSum + (p.mWakeStart != 0 ? (realtime - p.mWakeStart) : 0); + } + } + return 0; + } + + public void reportExcessiveWakeLocked(int uid, String proc, long overTime, long usedTime) { + Uid u = mUidStats.get(uid); + if (u != null) { + u.reportExcessiveWakeLocked(proc, overTime, usedTime); + } + } + + int mSensorNesting; + + public void noteStartSensorLocked(int uid, int sensor) { + if (mSensorNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + mSensorNesting++; + getUidStatsLocked(uid).noteStartSensor(sensor); + } + + public void noteStopSensorLocked(int uid, int sensor) { + mSensorNesting--; + if (mSensorNesting == 0) { + mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + getUidStatsLocked(uid).noteStopSensor(sensor); + } + int mGpsNesting; public void noteStartGpsLocked(int uid) { @@ -1246,6 +1357,10 @@ public final class BatteryStatsImpl extends BatteryStats { if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this); } + + // Fake a wake lock, so we consider the device waked as long + // as the screen is on. + noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); } } @@ -1260,6 +1375,8 @@ public final class BatteryStatsImpl extends BatteryStats { if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this); } + + noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); } } @@ -1800,6 +1917,11 @@ public final class BatteryStatsImpl extends BatteryStats { */ final HashMap<String, Pkg> mPackageStats = new HashMap<String, Pkg>(); + /** + * The transient wake stats we have collected for this uid's pids. + */ + final SparseArray<Pid> mPids = new SparseArray<Pid>(); + public Uid(int uid) { mUid = uid; mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables); @@ -2083,27 +2205,27 @@ public final class BatteryStatsImpl extends BatteryStats { boolean active = false; if (mWifiTurnedOnTimer != null) { - active |= !mWifiTurnedOnTimer.reset(false); + active |= !mWifiTurnedOnTimer.reset(BatteryStatsImpl.this, false); active |= mWifiTurnedOn; } if (mFullWifiLockTimer != null) { - active |= !mFullWifiLockTimer.reset(false); + active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false); active |= mFullWifiLockOut; } if (mScanWifiLockTimer != null) { - active |= !mScanWifiLockTimer.reset(false); + active |= !mScanWifiLockTimer.reset(BatteryStatsImpl.this, false); active |= mScanWifiLockOut; } if (mWifiMulticastTimer != null) { - active |= !mWifiMulticastTimer.reset(false); + active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false); active |= mWifiMulticastEnabled; } if (mAudioTurnedOnTimer != null) { - active |= !mAudioTurnedOnTimer.reset(false); + active |= !mAudioTurnedOnTimer.reset(BatteryStatsImpl.this, false); active |= mAudioTurnedOn; } if (mVideoTurnedOnTimer != null) { - active |= !mVideoTurnedOnTimer.reset(false); + active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false); active |= mVideoTurnedOn; } @@ -2148,6 +2270,14 @@ public final class BatteryStatsImpl extends BatteryStats { } mProcessStats.clear(); } + if (mPids.size() > 0) { + for (int i=0; !active && i<mPids.size(); i++) { + Pid pid = mPids.valueAt(i); + if (pid.mWakeStart != 0) { + active = true; + } + } + } if (mPackageStats.size() > 0) { Iterator<Map.Entry<String, Pkg>> it = mPackageStats.entrySet().iterator(); while (it.hasNext()) { @@ -2166,6 +2296,8 @@ public final class BatteryStatsImpl extends BatteryStats { mPackageStats.clear(); } + mPids.clear(); + if (!active) { if (mWifiTurnedOnTimer != null) { mWifiTurnedOnTimer.detach(); @@ -2414,13 +2546,13 @@ public final class BatteryStatsImpl extends BatteryStats { boolean reset() { boolean wlactive = false; if (mTimerFull != null) { - wlactive |= !mTimerFull.reset(false); + wlactive |= !mTimerFull.reset(BatteryStatsImpl.this, false); } if (mTimerPartial != null) { - wlactive |= !mTimerPartial.reset(false); + wlactive |= !mTimerPartial.reset(BatteryStatsImpl.this, false); } if (mTimerWindow != null) { - wlactive |= !mTimerWindow.reset(false); + wlactive |= !mTimerWindow.reset(BatteryStatsImpl.this, false); } if (!wlactive) { if (mTimerFull != null) { @@ -2488,7 +2620,7 @@ public final class BatteryStatsImpl extends BatteryStats { } boolean reset() { - if (mTimer.reset(true)) { + if (mTimer.reset(BatteryStatsImpl.this, true)) { mTimer = null; return true; } @@ -2600,6 +2732,8 @@ public final class BatteryStatsImpl extends BatteryStats { SamplingCounter[] mSpeedBins; + ArrayList<ExcessiveWake> mExcessiveWake; + Proc() { mUnpluggables.add(this); mSpeedBins = new SamplingCounter[getCpuSpeedSteps()]; @@ -2626,6 +2760,58 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public int countExcessiveWakes() { + return mExcessiveWake != null ? mExcessiveWake.size() : 0; + } + + public ExcessiveWake getExcessiveWake(int i) { + if (mExcessiveWake != null) { + return mExcessiveWake.get(i); + } + return null; + } + + public void addExcessiveWake(long overTime, long usedTime) { + if (mExcessiveWake == null) { + mExcessiveWake = new ArrayList<ExcessiveWake>(); + } + ExcessiveWake ew = new ExcessiveWake(); + ew.overTime = overTime; + ew.usedTime = usedTime; + mExcessiveWake.add(ew); + } + + void writeExcessiveWakeToParcelLocked(Parcel out) { + if (mExcessiveWake == null) { + out.writeInt(0); + return; + } + + final int N = mExcessiveWake.size(); + out.writeInt(N); + for (int i=0; i<N; i++) { + ExcessiveWake ew = mExcessiveWake.get(i); + out.writeLong(ew.overTime); + out.writeLong(ew.usedTime); + } + } + + void readExcessiveWakeFromParcelLocked(Parcel in) { + final int N = in.readInt(); + if (N == 0) { + mExcessiveWake = null; + return; + } + + mExcessiveWake = new ArrayList<ExcessiveWake>(); + for (int i=0; i<N; i++) { + ExcessiveWake ew = new ExcessiveWake(); + ew.overTime = in.readLong(); + ew.usedTime = in.readLong(); + mExcessiveWake.add(ew); + } + } + void writeToParcelLocked(Parcel out) { out.writeLong(mUserTime); out.writeLong(mSystemTime); @@ -2650,6 +2836,8 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(0); } } + + writeExcessiveWakeToParcelLocked(out); } void readFromParcelLocked(Parcel in) { @@ -2678,6 +2866,8 @@ public final class BatteryStatsImpl extends BatteryStats { mSpeedBins[i] = new SamplingCounter(mUnpluggables, in); } } + + readExcessiveWakeFromParcelLocked(in); } public BatteryStatsImpl getBatteryStats() { @@ -3155,6 +3345,11 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public class Pid { + long mWakeSum; + long mWakeStart; + } + /** * Retrieve the statistics object for a particular process, creating * if needed. @@ -3169,6 +3364,15 @@ public final class BatteryStatsImpl extends BatteryStats { return ps; } + public Pid getPidStatsLocked(int pid) { + Pid p = mPids.get(pid); + if (p == null) { + p = new Pid(); + mPids.put(pid, p); + } + return p; + } + /** * Retrieve the statistics object for a particular service, creating * if needed. @@ -3261,18 +3465,36 @@ public final class BatteryStatsImpl extends BatteryStats { return t; } - public void noteStartWakeLocked(String name, int type) { + public void noteStartWakeLocked(int pid, String name, int type) { StopwatchTimer t = getWakeTimerLocked(name, type); if (t != null) { t.startRunningLocked(BatteryStatsImpl.this); } + if (pid >= 0 && type == WAKE_TYPE_PARTIAL) { + Pid p = getPidStatsLocked(pid); + p.mWakeStart = SystemClock.elapsedRealtime(); + } } - public void noteStopWakeLocked(String name, int type) { + public void noteStopWakeLocked(int pid, String name, int type) { StopwatchTimer t = getWakeTimerLocked(name, type); if (t != null) { t.stopRunningLocked(BatteryStatsImpl.this); } + if (pid >= 0 && type == WAKE_TYPE_PARTIAL) { + Pid p = mPids.get(pid); + if (p != null) { + p.mWakeSum += SystemClock.elapsedRealtime() - p.mWakeStart; + p.mWakeStart = 0; + } + } + } + + public void reportExcessiveWakeLocked(String proc, long overTime, long usedTime) { + Proc p = getProcessStatsLocked(proc); + if (p != null) { + p.addExcessiveWake(overTime, usedTime); + } } public void noteStartSensor(int sensor) { @@ -3374,6 +3596,10 @@ public final class BatteryStatsImpl extends BatteryStats { return mOnBattery; } + public boolean isScreenOn() { + return mScreenOn; + } + void initTimes() { mBatteryRealtime = mTrackBatteryPastUptime = 0; mBatteryUptime = mTrackBatteryPastRealtime = 0; @@ -3386,24 +3612,24 @@ public final class BatteryStatsImpl extends BatteryStats { public void resetAllStatsLocked() { mStartCount = 0; initTimes(); - mScreenOnTimer.reset(false); + mScreenOnTimer.reset(this, false); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i].reset(false); + mScreenBrightnessTimer[i].reset(this, false); } mInputEventCounter.reset(false); - mPhoneOnTimer.reset(false); - mAudioOnTimer.reset(false); - mVideoOnTimer.reset(false); + mPhoneOnTimer.reset(this, false); + mAudioOnTimer.reset(this, false); + mVideoOnTimer.reset(this, false); for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i].reset(false); + mPhoneSignalStrengthsTimer[i].reset(this, false); } - mPhoneSignalScanningTimer.reset(false); + mPhoneSignalScanningTimer.reset(this, false); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i].reset(false); + mPhoneDataConnectionsTimer[i].reset(this, false); } - mWifiOnTimer.reset(false); - mWifiRunningTimer.reset(false); - mBluetoothOnTimer.reset(false); + mWifiOnTimer.reset(this, false); + mWifiRunningTimer.reset(this, false); + mBluetoothOnTimer.reset(this, false); for (int i=0; i<mUidStats.size(); i++) { if (mUidStats.valueAt(i).reset()) { @@ -4085,6 +4311,7 @@ public final class BatteryStatsImpl extends BatteryStats { p.mUserTime = p.mLoadedUserTime = in.readLong(); p.mSystemTime = p.mLoadedSystemTime = in.readLong(); p.mStarts = p.mLoadedStarts = in.readInt(); + p.readExcessiveWakeFromParcelLocked(in); } NP = in.readInt(); @@ -4273,6 +4500,7 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(ps.mUserTime); out.writeLong(ps.mSystemTime); out.writeInt(ps.mStarts); + ps.writeExcessiveWakeToParcelLocked(out); } } diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java index ab80c58..d381901 100644 --- a/core/java/com/android/internal/view/StandaloneActionMode.java +++ b/core/java/com/android/internal/view/StandaloneActionMode.java @@ -44,7 +44,7 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call mContextView = view; mCallback = callback; - mMenu = new MenuBuilder(context); + mMenu = new MenuBuilder(context).setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); mMenu.setCallback(this); } diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 215d809..d160fec 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -171,6 +171,11 @@ public class MenuBuilder implements Menu { private boolean mReserveActionOverflow; /** + * Default value for how added items should show in the action list. + */ + private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER; + + /** * Current use case is Context Menus: As Views populate the context menu, each one has * extra information that should be passed along. This is the current menu info that * should be set on all items added to this menu. @@ -328,6 +333,11 @@ public class MenuBuilder implements Menu { (mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS); } + public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) { + mDefaultShowAsAction = defaultShowAsAction; + return this; + } + public void setCallback(Callback callback) { mCallback = callback; } @@ -411,7 +421,8 @@ public class MenuBuilder implements Menu { private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) { final int ordering = getOrdering(categoryOrder); - final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder, ordering, title); + final MenuItemImpl item = new MenuItemImpl(this, group, id, categoryOrder, + ordering, title, mDefaultShowAsAction); if (mCurrentMenuInfo != null) { // Pass along the current menu info diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index fecbd77..07a2a94 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -108,7 +108,7 @@ public final class MenuItemImpl implements MenuItem { * @param title The text to display for the item. */ MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering, - CharSequence title) { + CharSequence title, int showAsAction) { if (sPrependShortcutLabel == null) { // This is instantiated from the UI thread, so no chance of sync issues @@ -129,6 +129,7 @@ public final class MenuItemImpl implements MenuItem { mCategoryOrder = categoryOrder; mOrdering = ordering; mTitle = title; + mShowAsAction = showAsAction; } /** diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 8918a8e..acf75e3 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -78,6 +78,12 @@ public class ActionBarContextView extends ViewGroup { a.recycle(); } + @Override + public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { + // No starting an action mode for an existing action mode UI child! (Where would it go?) + return null; + } + public void setHeight(int height) { mContentHeight = height; } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index c3c0db2..067d022 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -22,8 +22,8 @@ import com.android.internal.view.menu.ActionMenuView; import com.android.internal.view.menu.MenuBuilder; import android.app.ActionBar; -import android.app.Activity; import android.app.ActionBar.NavigationCallback; +import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -34,6 +34,7 @@ import android.graphics.drawable.Drawable; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.view.ActionMode; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -163,7 +164,6 @@ public class ActionBarView extends ViewGroup { LayoutInflater inflater = LayoutInflater.from(context); mCustomNavView = (View) inflater.inflate(customNavId, null); mNavigationMode = ActionBar.NAVIGATION_MODE_CUSTOM; - addView(mCustomNavView); } mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0); @@ -188,6 +188,12 @@ public class ActionBarView extends ViewGroup { } } + @Override + public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { + // No starting an action mode for an action bar child! (Where would it go?) + return null; + } + public void setCallback(NavigationCallback callback) { mCallback = callback; } @@ -553,7 +559,7 @@ public class ActionBarView extends ViewGroup { if (mSpinner != null) { mSpinner.measure( MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); } break; case ActionBar.NAVIGATION_MODE_CUSTOM: @@ -563,8 +569,16 @@ public class ActionBarView extends ViewGroup { MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; final int customNavWidth = lp.width >= 0 ? Math.min(lp.width, availableWidth) : availableWidth; - final int customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ? - MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; + + // If the action bar is wrapping to its content height, don't allow a custom + // view to MATCH_PARENT. + int customNavHeightMode; + if (mContentHeight <= 0) { + customNavHeightMode = MeasureSpec.AT_MOST; + } else { + customNavHeightMode = lp.height != LayoutParams.WRAP_CONTENT ? + MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; + } final int customNavHeight = lp.height >= 0 ? Math.min(lp.height, height) : height; mCustomNavView.measure( @@ -576,7 +590,7 @@ public class ActionBarView extends ViewGroup { if (mTabLayout != null) { mTabLayout.measure( MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); } break; } diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl index 7851347..ae9900c 100644 --- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl +++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl @@ -20,6 +20,7 @@ import android.widget.RemoteViews; /** {@hide} */ interface IRemoteViewsFactory { + void onDataSetChanged(); int getCount(); RemoteViews getViewAt(int position); RemoteViews getLoadingView(); diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java index 3218ba8..4b56cb4 100644 --- a/core/java/com/android/internal/widget/SlidingTab.java +++ b/core/java/com/android/internal/widget/SlidingTab.java @@ -474,10 +474,13 @@ public class SlidingTab extends ViewGroup { int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { - Log.e("SlidingTab", "SlidingTab cannot have UNSPECIFIED MeasureSpec" - +"(wspec=" + widthSpecMode + ", hspec=" + heightSpecMode + ")", - new RuntimeException(LOG_TAG + "stack:")); + if (DBG) { + if (widthSpecMode == MeasureSpec.UNSPECIFIED + || heightSpecMode == MeasureSpec.UNSPECIFIED) { + Log.e("SlidingTab", "SlidingTab cannot have UNSPECIFIED MeasureSpec" + +"(wspec=" + widthSpecMode + ", hspec=" + heightSpecMode + ")", + new RuntimeException(LOG_TAG + "stack:")); + } } mLeftSlider.measure(); diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 860b5b7..cb0bdd3 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -82,6 +82,7 @@ LOCAL_SRC_FILES:= \ android_util_Process.cpp \ android_util_StringBlock.cpp \ android_util_XmlBlock.cpp \ + android/graphics/AutoDecodeCancel.cpp \ android/graphics/Bitmap.cpp \ android/graphics/BitmapFactory.cpp \ android/graphics/Camera.cpp \ @@ -105,6 +106,7 @@ LOCAL_SRC_FILES:= \ android_graphics_PixelFormat.cpp \ android/graphics/Picture.cpp \ android/graphics/PorterDuff.cpp \ + android/graphics/LargeBitmap.cpp \ android/graphics/Rasterizer.cpp \ android/graphics/Region.cpp \ android/graphics/Shader.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 2dd17bb..dba1cea 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -53,6 +53,7 @@ extern int register_android_os_Binder(JNIEnv* env); extern int register_android_os_Process(JNIEnv* env); extern int register_android_graphics_Bitmap(JNIEnv*); extern int register_android_graphics_BitmapFactory(JNIEnv*); +extern int register_android_graphics_LargeBitmap(JNIEnv*); extern int register_android_graphics_Camera(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_Interpolator(JNIEnv* env); @@ -1256,6 +1257,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Bitmap), REG_JNI(register_android_graphics_BitmapFactory), + REG_JNI(register_android_graphics_LargeBitmap), REG_JNI(register_android_graphics_Camera), REG_JNI(register_android_graphics_Canvas), REG_JNI(register_android_graphics_ColorFilter), diff --git a/core/jni/android/graphics/AutoDecodeCancel.cpp b/core/jni/android/graphics/AutoDecodeCancel.cpp new file mode 100644 index 0000000..f0739ea --- /dev/null +++ b/core/jni/android/graphics/AutoDecodeCancel.cpp @@ -0,0 +1,100 @@ +#include "AutoDecodeCancel.h" + +static SkMutex gAutoDecoderCancelMutex; +static AutoDecoderCancel* gAutoDecoderCancel; +#ifdef SK_DEBUG +static int gAutoDecoderCancelCount; +#endif + +AutoDecoderCancel::AutoDecoderCancel(jobject joptions, + SkImageDecoder* decoder) { + fJOptions = joptions; + fDecoder = decoder; + + if (NULL != joptions) { + SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); + + // Add us as the head of the list + fPrev = NULL; + fNext = gAutoDecoderCancel; + if (gAutoDecoderCancel) { + gAutoDecoderCancel->fPrev = this; + } + gAutoDecoderCancel = this; + + SkDEBUGCODE(gAutoDecoderCancelCount += 1;) + Validate(); + } +} + +AutoDecoderCancel::~AutoDecoderCancel() { + if (NULL != fJOptions) { + SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); + + // take us out of the dllist + AutoDecoderCancel* prev = fPrev; + AutoDecoderCancel* next = fNext; + + if (prev) { + SkASSERT(prev->fNext == this); + prev->fNext = next; + } else { + SkASSERT(gAutoDecoderCancel == this); + gAutoDecoderCancel = next; + } + if (next) { + SkASSERT(next->fPrev == this); + next->fPrev = prev; + } + + SkDEBUGCODE(gAutoDecoderCancelCount -= 1;) + Validate(); + } +} + +bool AutoDecoderCancel::RequestCancel(jobject joptions) { + SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); + + Validate(); + + AutoDecoderCancel* pair = gAutoDecoderCancel; + while (pair != NULL) { + if (pair->fJOptions == joptions) { + pair->fDecoder->cancelDecode(); + return true; + } + pair = pair->fNext; + } + return false; +} + +#ifdef SK_DEBUG +// can only call this inside a lock on gAutoDecoderCancelMutex +void AutoDecoderCancel::Validate() { + const int gCount = gAutoDecoderCancelCount; + + if (gCount == 0) { + SkASSERT(gAutoDecoderCancel == NULL); + } else { + SkASSERT(gCount > 0); + + AutoDecoderCancel* curr = gAutoDecoderCancel; + SkASSERT(curr); + SkASSERT(curr->fPrev == NULL); + + int count = 0; + while (curr) { + count += 1; + SkASSERT(count <= gCount); + if (curr->fPrev) { + SkASSERT(curr->fPrev->fNext == curr); + } + if (curr->fNext) { + SkASSERT(curr->fNext->fPrev == curr); + } + curr = curr->fNext; + } + SkASSERT(count == gCount); + } +} +#endif diff --git a/core/jni/android/graphics/AutoDecodeCancel.h b/core/jni/android/graphics/AutoDecodeCancel.h new file mode 100644 index 0000000..37b86f9 --- /dev/null +++ b/core/jni/android/graphics/AutoDecodeCancel.h @@ -0,0 +1,27 @@ +#ifndef AutoDecodeCancel_DEFINED +#define AutoDecodeCancel_DEFINED + +#include <jni.h> +#include "SkImageDecoder.h" + +class AutoDecoderCancel { +public: + AutoDecoderCancel(jobject options, SkImageDecoder* decoder); + ~AutoDecoderCancel(); + + static bool RequestCancel(jobject options); + +private: + AutoDecoderCancel* fNext; + AutoDecoderCancel* fPrev; + jobject fJOptions; // java options object + SkImageDecoder* fDecoder; + +#ifdef SK_DEBUG + static void Validate(); +#else + static void Validate() {} +#endif +}; + +#endif diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index b41bad0..21b2e3b 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -1,14 +1,15 @@ #define LOG_TAG "BitmapFactory" +#include "BitmapFactory.h" #include "SkImageDecoder.h" #include "SkImageRef_ashmem.h" #include "SkImageRef_GlobalPool.h" #include "SkPixelRef.h" #include "SkStream.h" -#include "GraphicsJNI.h" #include "SkTemplates.h" #include "SkUtils.h" #include "CreateJavaOutputStreamAdaptor.h" +#include "AutoDecodeCancel.h" #include <android_runtime/AndroidRuntime.h> #include <utils/Asset.h> @@ -16,18 +17,18 @@ #include <netinet/in.h> #include <sys/mman.h> -static jclass gOptions_class; -static jfieldID gOptions_justBoundsFieldID; -static jfieldID gOptions_sampleSizeFieldID; -static jfieldID gOptions_configFieldID; -static jfieldID gOptions_ditherFieldID; -static jfieldID gOptions_purgeableFieldID; -static jfieldID gOptions_shareableFieldID; -static jfieldID gOptions_nativeAllocFieldID; -static jfieldID gOptions_widthFieldID; -static jfieldID gOptions_heightFieldID; -static jfieldID gOptions_mimeFieldID; -static jfieldID gOptions_mCancelID; +jclass gOptions_class; +jfieldID gOptions_justBoundsFieldID; +jfieldID gOptions_sampleSizeFieldID; +jfieldID gOptions_configFieldID; +jfieldID gOptions_ditherFieldID; +jfieldID gOptions_purgeableFieldID; +jfieldID gOptions_shareableFieldID; +jfieldID gOptions_nativeAllocFieldID; +jfieldID gOptions_widthFieldID; +jfieldID gOptions_heightFieldID; +jfieldID gOptions_mimeFieldID; +jfieldID gOptions_mCancelID; static jclass gFileDescriptor_class; static jfieldID gFileDescriptor_descriptor; @@ -38,129 +39,6 @@ static jfieldID gFileDescriptor_descriptor; #define TRACE_BITMAP(code) #endif -/////////////////////////////////////////////////////////////////////////////// - -class AutoDecoderCancel { -public: - AutoDecoderCancel(jobject options, SkImageDecoder* decoder); - ~AutoDecoderCancel(); - - static bool RequestCancel(jobject options); - -private: - AutoDecoderCancel* fNext; - AutoDecoderCancel* fPrev; - jobject fJOptions; // java options object - SkImageDecoder* fDecoder; - -#ifdef SK_DEBUG - static void Validate(); -#else - static void Validate() {} -#endif -}; - -static SkMutex gAutoDecoderCancelMutex; -static AutoDecoderCancel* gAutoDecoderCancel; -#ifdef SK_DEBUG - static int gAutoDecoderCancelCount; -#endif - -AutoDecoderCancel::AutoDecoderCancel(jobject joptions, - SkImageDecoder* decoder) { - fJOptions = joptions; - fDecoder = decoder; - - if (NULL != joptions) { - SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); - - // Add us as the head of the list - fPrev = NULL; - fNext = gAutoDecoderCancel; - if (gAutoDecoderCancel) { - gAutoDecoderCancel->fPrev = this; - } - gAutoDecoderCancel = this; - - SkDEBUGCODE(gAutoDecoderCancelCount += 1;) - Validate(); - } -} - -AutoDecoderCancel::~AutoDecoderCancel() { - if (NULL != fJOptions) { - SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); - - // take us out of the dllist - AutoDecoderCancel* prev = fPrev; - AutoDecoderCancel* next = fNext; - - if (prev) { - SkASSERT(prev->fNext == this); - prev->fNext = next; - } else { - SkASSERT(gAutoDecoderCancel == this); - gAutoDecoderCancel = next; - } - if (next) { - SkASSERT(next->fPrev == this); - next->fPrev = prev; - } - - SkDEBUGCODE(gAutoDecoderCancelCount -= 1;) - Validate(); - } -} - -bool AutoDecoderCancel::RequestCancel(jobject joptions) { - SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); - - Validate(); - - AutoDecoderCancel* pair = gAutoDecoderCancel; - while (pair != NULL) { - if (pair->fJOptions == joptions) { - pair->fDecoder->cancelDecode(); - return true; - } - pair = pair->fNext; - } - return false; -} - -#ifdef SK_DEBUG -// can only call this inside a lock on gAutoDecoderCancelMutex -void AutoDecoderCancel::Validate() { - const int gCount = gAutoDecoderCancelCount; - - if (gCount == 0) { - SkASSERT(gAutoDecoderCancel == NULL); - } else { - SkASSERT(gCount > 0); - - AutoDecoderCancel* curr = gAutoDecoderCancel; - SkASSERT(curr); - SkASSERT(curr->fPrev == NULL); - - int count = 0; - while (curr) { - count += 1; - SkASSERT(count <= gCount); - if (curr->fPrev) { - SkASSERT(curr->fPrev->fNext == curr); - } - if (curr->fNext) { - SkASSERT(curr->fNext->fPrev == curr); - } - curr = curr->fNext; - } - SkASSERT(count == gCount); - } -} -#endif - -/////////////////////////////////////////////////////////////////////////////// - using namespace android; class NinePatchPeeker : public SkImageDecoder::Peeker { @@ -279,7 +157,7 @@ static inline int32_t validOrNeg1(bool isValid, int32_t value) { return ((int32_t)isValid - 1) | value; } -static jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) { +jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) { static const struct { SkImageDecoder::Format fFormat; const char* fMimeType; @@ -477,7 +355,7 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject padding, jobject options) { // BitmapFactory$Options jobject bitmap = NULL; - SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage); + SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0); if (stream) { // for now we don't allow purgeable with java inputstreams @@ -682,6 +560,107 @@ static void nativeSetDefaultConfig(JNIEnv* env, jobject, int nativeConfig) { } } +static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream, bool isShareable) { + SkImageDecoder* decoder = SkImageDecoder::Factory(stream); + int width, height; + if (NULL == decoder) { + doThrowIOE(env, "Image format not supported"); + return nullObjectReturn("SkImageDecoder::Factory returned null"); + } + + JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env, true); + decoder->setAllocator(javaAllocator); + JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env); + decoder->setReporter(javaMemoryReporter); + javaAllocator->unref(); + javaMemoryReporter->unref(); + + if (!decoder->buildTileIndex(stream, &width, &height, isShareable)) { + char msg[1024]; + snprintf(msg, 1023, "Image failed to decode using %s decoder", decoder->getFormatName()); + doThrowIOE(env, msg); + return nullObjectReturn("decoder->buildTileIndex returned false"); + } + + SkLargeBitmap *bm = new SkLargeBitmap(decoder, width, height); + + return GraphicsJNI::createLargeBitmap(env, bm); +} + +static jobject nativeCreateLargeBitmapFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray, + int offset, int length, jboolean isShareable) { + AutoJavaByteArray ar(env, byteArray); + SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, false); + SkAutoUnref aur(stream); + if (isShareable) { + aur.detach(); + } + return doBuildTileIndex(env, stream, isShareable); +} + +static jobject nativeCreateLargeBitmapFromFileDescriptor(JNIEnv* env, jobject clazz, + jobject fileDescriptor, jboolean isShareable) { + NPE_CHECK_RETURN_ZERO(env, fileDescriptor); + + jint descriptor = env->GetIntField(fileDescriptor, + gFileDescriptor_descriptor); + bool weOwnTheFD = false; + + if (isShareable) { + int newFD = ::dup(descriptor); + if (-1 != newFD) { + weOwnTheFD = true; + descriptor = newFD; + } + } + + SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD); + SkAutoUnref aur(stream); + if (!stream->isValid()) { + return NULL; + } + + if (isShareable) { + aur.detach(); + } + + /* Restore our offset when we leave, so we can be called more than once + with the same descriptor. This is only required if we didn't dup the + file descriptor, but it is OK to do it all the time. + */ + AutoFDSeek as(descriptor); + + return doBuildTileIndex(env, stream, isShareable); +} + +static jobject nativeCreateLargeBitmapFromStream(JNIEnv* env, jobject clazz, + jobject is, // InputStream + jbyteArray storage, // byte[] + jboolean isShareable) { + jobject largeBitmap = NULL; + SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024); + + if (stream) { + // for now we don't allow shareable with java inputstreams + largeBitmap = doBuildTileIndex(env, stream, false); + stream->unref(); + } + return largeBitmap; +} + +static jobject nativeCreateLargeBitmapFromAsset(JNIEnv* env, jobject clazz, + jint native_asset, // Asset + jboolean isShareable) { + SkStream* stream; + Asset* asset = reinterpret_cast<Asset*>(native_asset); + stream = new AssetStreamAdaptor(asset); + SkAutoUnref aur(stream); + if (isShareable) { + aur.detach(); + } + return doBuildTileIndex(env, stream, isShareable); +} + /////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gMethods[] = { @@ -711,6 +690,26 @@ static JNINativeMethod gMethods[] = { }, { "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig }, + + { "nativeCreateLargeBitmap", + "([BIIZ)Landroid/graphics/LargeBitmap;", + (void*)nativeCreateLargeBitmapFromByteArray + }, + + { "nativeCreateLargeBitmap", + "(Ljava/io/InputStream;[BZ)Landroid/graphics/LargeBitmap;", + (void*)nativeCreateLargeBitmapFromStream + }, + + { "nativeCreateLargeBitmap", + "(Ljava/io/FileDescriptor;Z)Landroid/graphics/LargeBitmap;", + (void*)nativeCreateLargeBitmapFromFileDescriptor + }, + + { "nativeCreateLargeBitmap", + "(IZ)Landroid/graphics/LargeBitmap;", + (void*)nativeCreateLargeBitmapFromAsset + }, }; static JNINativeMethod gOptionsMethods[] = { diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h new file mode 100644 index 0000000..f868434 --- /dev/null +++ b/core/jni/android/graphics/BitmapFactory.h @@ -0,0 +1,21 @@ +#ifndef BitmapFactory_DEFINE +#define BitmapFactory_DEFINE + +#include "GraphicsJNI.h" + +extern jclass gOptions_class; +extern jfieldID gOptions_justBoundsFieldID; +extern jfieldID gOptions_sampleSizeFieldID; +extern jfieldID gOptions_configFieldID; +extern jfieldID gOptions_ditherFieldID; +extern jfieldID gOptions_purgeableFieldID; +extern jfieldID gOptions_shareableFieldID; +extern jfieldID gOptions_nativeAllocFieldID; +extern jfieldID gOptions_widthFieldID; +extern jfieldID gOptions_heightFieldID; +extern jfieldID gOptions_mimeFieldID; +extern jfieldID gOptions_mCancelID; + +jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format); + +#endif diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp index 007757f..137acc6 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp @@ -5,6 +5,7 @@ static jclass gInputStream_Clazz; static jmethodID gInputStream_resetMethodID; +static jmethodID gInputStream_markMethodID; static jmethodID gInputStream_availableMethodID; static jmethodID gInputStream_readMethodID; static jmethodID gInputStream_skipMethodID; @@ -143,7 +144,7 @@ private: }; SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage) { + jbyteArray storage, int markSize) { static bool gInited; if (!gInited) { @@ -153,6 +154,8 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, gInputStream_resetMethodID = env->GetMethodID(gInputStream_Clazz, "reset", "()V"); + gInputStream_markMethodID = env->GetMethodID(gInputStream_Clazz, + "mark", "(I)V"); gInputStream_availableMethodID = env->GetMethodID(gInputStream_Clazz, "available", "()I"); gInputStream_readMethodID = env->GetMethodID(gInputStream_Clazz, @@ -161,6 +164,7 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, "skip", "(J)J"); RETURN_NULL_IF_NULL(gInputStream_resetMethodID); + RETURN_NULL_IF_NULL(gInputStream_markMethodID); RETURN_NULL_IF_NULL(gInputStream_availableMethodID); RETURN_NULL_IF_NULL(gInputStream_availableMethodID); RETURN_NULL_IF_NULL(gInputStream_skipMethodID); @@ -168,6 +172,10 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, gInited = true; } + if (markSize) { + env->CallVoidMethod(stream, gInputStream_markMethodID, markSize); + } + return new JavaInputStreamAdaptor(env, stream, storage); } diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h index cf21dde..c34c96a 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h @@ -6,7 +6,7 @@ #include "SkStream.h" SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage); + jbyteArray storage, int markSize = 0); SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage); diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 5659ba2..204bb74 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -46,6 +46,10 @@ void doThrowOOME(JNIEnv* env, const char* msg) { doThrow(env, "java/lang/OutOfMemoryError", msg); } +void doThrowIOE(JNIEnv* env, const char* msg) { + doThrow(env, "java/lang/IOException", msg); +} + bool GraphicsJNI::hasException(JNIEnv *env) { if (env->ExceptionCheck() != 0) { LOGE("*** Uncaught exception returned from Java call!\n"); @@ -165,6 +169,9 @@ static jmethodID gBitmap_allocBufferMethodID; static jclass gBitmapConfig_class; static jfieldID gBitmapConfig_nativeInstanceID; +static jclass gLargeBitmap_class; +static jmethodID gLargeBitmap_constructorMethodID; + static jclass gCanvas_class; static jfieldID gCanvas_nativeInstanceID; @@ -370,6 +377,23 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, } return obj; } +jobject GraphicsJNI::createLargeBitmap(JNIEnv* env, SkLargeBitmap* bitmap) +{ + SkASSERT(bitmap != NULL); + + jobject obj = env->AllocObject(gLargeBitmap_class); + if (hasException(env)) { + obj = NULL; + return obj; + } + if (obj) { + env->CallVoidMethod(obj, gLargeBitmap_constructorMethodID, (jint)bitmap); + if (hasException(env)) { + obj = NULL; + } + } + return obj; +} jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region) { @@ -502,6 +526,35 @@ bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { //////////////////////////////////////////////////////////////////////////////// +JavaMemoryUsageReporter::JavaMemoryUsageReporter(JNIEnv* env) + : fEnv(env), fTotalSize(0) {} + +JavaMemoryUsageReporter::~JavaMemoryUsageReporter() { + jlong jtotalSize = fTotalSize; + fEnv->CallVoidMethod(gVMRuntime_singleton, + gVMRuntime_trackExternalFreeMethodID, + jtotalSize); +} + +bool JavaMemoryUsageReporter::reportMemory(size_t memorySize) { + jlong jsize = memorySize; // the VM wants longs for the size + bool r = fEnv->CallBooleanMethod(gVMRuntime_singleton, + gVMRuntime_trackExternalAllocationMethodID, + jsize); + if (GraphicsJNI::hasException(fEnv)) { + return false; + } + if (!r) { + LOGE("VM won't let us allocate %zd bytes\n", memorySize); + doThrowOOME(fEnv, "bitmap size exceeds VM budget"); + return false; + } + fTotalSize += memorySize; + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + static jclass make_globalref(JNIEnv* env, const char classname[]) { jclass c = env->FindClass(classname); @@ -547,6 +600,9 @@ int register_android_graphics_Graphics(JNIEnv* env) gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(IZ[BI)V"); + gLargeBitmap_class = make_globalref(env, "android/graphics/LargeBitmap"); + gLargeBitmap_constructorMethodID = env->GetMethodID(gLargeBitmap_class, "<init>", "(I)V"); + gBitmapConfig_class = make_globalref(env, "android/graphics/Bitmap$Config"); gBitmapConfig_nativeInstanceID = getFieldIDCheck(env, gBitmapConfig_class, "nativeInt", "I"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index fe24b05..8d6528b 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -4,6 +4,8 @@ #include "SkPoint.h" #include "SkRect.h" #include "SkBitmap.h" +#include "../images/SkLargeBitmap.h" +#include "../images/SkImageDecoder.h" #include <jni.h> class SkCanvas; @@ -54,6 +56,8 @@ public: static jobject createRegion(JNIEnv* env, SkRegion* region); + static jobject createLargeBitmap(JNIEnv* env, SkLargeBitmap* bitmap); + /** Set a pixelref for the bitmap (needs setConfig to already be called) Returns true on success. If it returns false, then it failed, and the appropriate exception will have been raised. @@ -80,6 +84,18 @@ private: bool fReportSizeToVM; }; +class JavaMemoryUsageReporter : public SkVMMemoryReporter { +public: + JavaMemoryUsageReporter(JNIEnv* env); + virtual ~JavaMemoryUsageReporter(); + // overrides + virtual bool reportMemory(size_t memorySize); + +private: + JNIEnv* fEnv; + size_t fTotalSize; +}; + enum JNIAccess { kRO_JNIAccess, kRW_JNIAccess @@ -156,6 +172,7 @@ void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument void doThrowRE(JNIEnv* env, const char* msg = NULL); // Runtime void doThrowISE(JNIEnv* env, const char* msg = NULL); // Illegal State void doThrowOOME(JNIEnv* env, const char* msg = NULL); // Out of memory +void doThrowIOE(JNIEnv* env, const char* msg = NULL); // IO Exception #define NPE_CHECK_RETURN_ZERO(env, object) \ do { if (NULL == (object)) { doThrowNPE(env); return 0; } } while (0) diff --git a/core/jni/android/graphics/LargeBitmap.cpp b/core/jni/android/graphics/LargeBitmap.cpp new file mode 100644 index 0000000..4cf5dfa --- /dev/null +++ b/core/jni/android/graphics/LargeBitmap.cpp @@ -0,0 +1,138 @@ +#define LOG_TAG "LargeBitmap" + +#include "SkBitmap.h" +#include "SkImageEncoder.h" +#include "SkColorPriv.h" +#include "GraphicsJNI.h" +#include "SkDither.h" +#include "SkUnPreMultiply.h" +#include "SkUtils.h" +#include "SkTemplates.h" +#include "SkPixelRef.h" +#include "BitmapFactory.h" +#include "AutoDecodeCancel.h" +#include "SkLargeBitmap.h" + +#include <binder/Parcel.h> +#include "android_util_Binder.h" +#include "android_nio_utils.h" +#include "CreateJavaOutputStreamAdaptor.h" + +#include <jni.h> + +#if 0 + #define TRACE_BITMAP(code) code +#else + #define TRACE_BITMAP(code) +#endif + +static jobject nullObjectReturn(const char msg[]) { + if (msg) { + SkDebugf("--- %s\n", msg); + } + return NULL; +} + +/* + * nine patch not supported + * + * purgeable not supported + * reportSizeToVM not supported + */ +static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkLargeBitmap *bm, + int start_x, int start_y, int width, int height, jobject options) { + SkImageDecoder *decoder = bm->getDecoder(); + int sampleSize = 1; + SkBitmap::Config prefConfig = SkBitmap::kNo_Config; + bool doDither = true; + + if (NULL != options) { + sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); + // initialize these, in case we fail later on + env->SetIntField(options, gOptions_widthFieldID, -1); + env->SetIntField(options, gOptions_heightFieldID, -1); + env->SetObjectField(options, gOptions_mimeFieldID, 0); + + jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); + prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig); + doDither = env->GetBooleanField(options, gOptions_ditherFieldID); + } + + decoder->setDitherImage(doDither); + SkBitmap* bitmap = new SkBitmap; + SkAutoTDelete<SkBitmap> adb(bitmap); + AutoDecoderCancel adc(options, decoder); + + // To fix the race condition in case "requestCancelDecode" + // happens earlier than AutoDecoderCancel object is added + // to the gAutoDecoderCancelMutex linked list. + if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) { + return nullObjectReturn("gOptions_mCancelID");; + } + + SkIRect region; + region.fLeft = start_x; + region.fTop = start_y; + region.fRight = start_x + width; + region.fBottom = start_y + height; + + if (!bm->decodeRegion(bitmap, region, prefConfig, sampleSize)) { + return nullObjectReturn("decoder->decodeRegion returned false"); + } + + // update options (if any) + if (NULL != options) { + env->SetIntField(options, gOptions_widthFieldID, bitmap->width()); + env->SetIntField(options, gOptions_heightFieldID, bitmap->height()); + // TODO: set the mimeType field with the data from the codec. + // but how to reuse a set of strings, rather than allocating new one + // each time? + env->SetObjectField(options, gOptions_mimeFieldID, + getMimeTypeString(env, decoder->getFormat())); + } + + // detach bitmap from its autotdeleter, since we want to own it now + adb.detach(); + + SkPixelRef* pr; + pr = bitmap->pixelRef(); + // promise we will never change our pixels (great for sharing and pictures) + pr->setImmutable(); + // now create the java bitmap + return GraphicsJNI::createBitmap(env, bitmap, false, NULL); +} + +static int nativeGetHeight(JNIEnv* env, jobject, SkLargeBitmap *bm) { + return bm->getHeight(); +} + +static int nativeGetWidth(JNIEnv* env, jobject, SkLargeBitmap *bm) { + return bm->getWidth(); +} + +static void nativeClean(JNIEnv* env, jobject, SkLargeBitmap *bm) { + delete bm; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include <android_runtime/AndroidRuntime.h> + +static JNINativeMethod gLargeBitmapMethods[] = { + { "nativeDecodeRegion", + "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", + (void*)nativeDecodeRegion}, + { "nativeGetHeight", "(I)I", (void*)nativeGetHeight}, + { "nativeGetWidth", "(I)I", (void*)nativeGetWidth}, + { "nativeClean", "(I)V", (void*)nativeClean}, +}; + +#define kClassPathName "android/graphics/LargeBitmap" + +int register_android_graphics_LargeBitmap(JNIEnv* env); +int register_android_graphics_LargeBitmap(JNIEnv* env) +{ + return android::AndroidRuntime::registerNativeMethods(env, kClassPathName, + gLargeBitmapMethods, SK_ARRAY_COUNT(gLargeBitmapMethods)); +} + diff --git a/core/res/res/drawable-hdpi/cursor_controller.png b/core/res/res/drawable-hdpi/cursor_controller.png Binary files differdeleted file mode 100644 index 720aded..0000000 --- a/core/res/res/drawable-hdpi/cursor_controller.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/selection_end_handle.png b/core/res/res/drawable-hdpi/selection_end_handle.png Binary files differdeleted file mode 100644 index 624ab58..0000000 --- a/core/res/res/drawable-hdpi/selection_end_handle.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/selection_start_handle.png b/core/res/res/drawable-hdpi/selection_start_handle.png Binary files differdeleted file mode 100644 index 7d6f24c..0000000 --- a/core/res/res/drawable-hdpi/selection_start_handle.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/text_select_handle.png b/core/res/res/drawable-hdpi/text_select_handle.png Binary files differnew file mode 100644 index 0000000..93a5a15 --- /dev/null +++ b/core/res/res/drawable-hdpi/text_select_handle.png diff --git a/core/res/res/drawable-mdpi/cursor_controller.png b/core/res/res/drawable-mdpi/cursor_controller.png Binary files differdeleted file mode 100644 index 1a8a459..0000000 --- a/core/res/res/drawable-mdpi/cursor_controller.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/selection_end_handle.png b/core/res/res/drawable-mdpi/selection_end_handle.png Binary files differdeleted file mode 100644 index 7e075eb..0000000 --- a/core/res/res/drawable-mdpi/selection_end_handle.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/selection_start_handle.png b/core/res/res/drawable-mdpi/selection_start_handle.png Binary files differdeleted file mode 100644 index d8022f7..0000000 --- a/core/res/res/drawable-mdpi/selection_start_handle.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/text_select_handle.png b/core/res/res/drawable-mdpi/text_select_handle.png Binary files differnew file mode 100644 index 0000000..93a5a15 --- /dev/null +++ b/core/res/res/drawable-mdpi/text_select_handle.png diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 2e7bfdf..4d42a4e 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -869,8 +869,8 @@ <string name="car_mode_disable_notification_message" msgid="668663626721675614">"選取結束車用模式。"</string> <string name="tethered_notification_title" msgid="3146694234398202601">"數據連線或無線基地台已啟用"</string> <string name="tethered_notification_message" msgid="3067108323903048927">"輕觸以設定"</string> - <string name="back_button_label" msgid="2300470004503343439">"上一頁"</string> - <string name="next_button_label" msgid="1080555104677992408">"下一頁"</string> + <string name="back_button_label" msgid="2300470004503343439">"返回"</string> + <string name="next_button_label" msgid="1080555104677992408">"繼續"</string> <!-- no translation found for skip_button_label (1275362299471631819) --> <skip /> <string name="throttle_warning_notification_title" msgid="4890894267454867276">"高行動資料用量"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 67327b2..4362c04 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -46,6 +46,7 @@ theme does not set this value, meaning it is based on whether the window is floating. --> <attr name="backgroundDimEnabled" format="boolean" /> + <!-- =========== --> <!-- Text styles --> @@ -95,6 +96,12 @@ <!-- Text color for urls in search suggestions, used by things like global search and the browser. @hide --> <attr name="textColorSearchUrl" format="reference|color" /> + + <!-- Color of highlighted text, when used in a light theme. @hide --> + <attr name="textColorHighlightInverse" format="reference|color" /> + <!-- Color of link text (URLs), when used in a light theme. @hide --> + <attr name="textColorLinkInverse" format="reference|color" /> + <!-- Search widget more corpus result item background. --> <attr name="searchWidgetCorpusItemBackground" format="reference|color" /> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index a2316e0..3a0c651 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -54,6 +54,12 @@ <color name="dim_foreground_light_inverse">#bebebe</color> <color name="dim_foreground_light_inverse_disabled">#80bebebe</color> <color name="hint_foreground_light">#808080</color> + <color name="highlight_background">#cc475925</color> + <color name="highlight_background_inverse">#ccd2e461</color> + <color name="highlighted_text_dark">#cc475925</color> + <color name="highlighted_text_light">#ccd2e461</color> + <color name="link_text_dark">#5c5cff</color> + <color name="link_text_light">#0000ee</color> <drawable name="stat_notify_sync_noanim">@drawable/stat_notify_sync_anim0</drawable> <drawable name="stat_sys_download_done">@drawable/stat_sys_download_anim0</drawable> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 91e9a92..05cae9f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -269,6 +269,21 @@ <!-- Default LED off time for notification LED in milliseconds. --> <integer name="config_defaultNotificationLedOff">2000</integer> + <!-- Default value for led color when battery is low on charge --> + <integer name="config_notificationsBatteryLowARGB">0xFFFF0000</integer> + + <!-- Default value for led color when battery is medium charged --> + <integer name="config_notificationsBatteryMediumARGB">0xFFFFFF00</integer> + + <!-- Default value for led color when battery is fully charged --> + <integer name="config_notificationsBatteryFullARGB">0xFF00FF00</integer> + + <!-- Default value for LED on time when the battery is low on charge in miliseconds --> + <integer name="config_notificationsBatteryLedOn">125</integer> + + <!-- Default value for LED off time when the battery is low on charge in miliseconds --> + <integer name="config_notificationsBatteryLedOff">2875</integer> + <!-- Allow the menu hard key to be disabled in LockScreen on some devices --> <bool name="config_disableMenuKeyInLockScreen">false</bool> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index fdafaf1..3b38bd5 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -47,6 +47,4 @@ <dimen name="password_keyboard_key_height_numeric">56dip</dimen> <!-- Default correction for the space key in the password keyboard --> <dimen name="password_keyboard_spacebar_vertical_correction">4dip</dimen> - <!-- Distance between the text base line and virtual finger position used to position cursor --> - <dimen name="cursor_controller_vertical_offset">12dp</dimen> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index b84a613..f413a12 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1329,7 +1329,7 @@ <!-- Context menu ID for the "Select text..." menu item to switch to text selection context mode in text views. --> <public type="id" name="selectTextMode" /> - + <public type="style" name="Theme.WithActionBar" /> <public type="style" name="Widget.Spinner.DropDown" /> <public type="style" name="Widget.ActionButton" /> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 2ae3ccd..4294c22 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -640,9 +640,9 @@ <style name="TextAppearance"> <item name="android:textColor">?textColorPrimary</item> - <item name="android:textColorHighlight">#D077A14B</item> + <item name="android:textColorHighlight">?textColorHighlight</item> <item name="android:textColorHint">?textColorHint</item> - <item name="android:textColorLink">#5C5CFF</item> + <item name="android:textColorLink">?textColorLink</item> <item name="android:textSize">16sp</item> <item name="android:textStyle">normal</item> </style> @@ -650,7 +650,8 @@ <style name="TextAppearance.Inverse"> <item name="textColor">?textColorPrimaryInverse</item> <item name="android:textColorHint">?textColorHintInverse</item> - <item name="android:textColorLink">#0000EE</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> </style> <style name="TextAppearance.Theme"> @@ -671,6 +672,8 @@ <style name="TextAppearance.Large.Inverse"> <item name="android:textColor">?textColorPrimaryInverse</item> <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> </style> <style name="TextAppearance.Medium"> @@ -682,6 +685,8 @@ <style name="TextAppearance.Medium.Inverse"> <item name="android:textColor">?textColorPrimaryInverse</item> <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> </style> <style name="TextAppearance.Small"> @@ -693,6 +698,8 @@ <style name="TextAppearance.Small.Inverse"> <item name="android:textColor">?textColorSecondaryInverse</item> <item name="android:textColorHint">?textColorHintInverse</item> + <item name="android:textColorHighlight">?textColorHighlightInverse</item> + <item name="android:textColorLink">?textColorLinkInverse</item> </style> <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme"> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index f9e20f7..348a7de 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -53,6 +53,10 @@ <item name="textColorHint">@android:color/hint_foreground_dark</item> <item name="textColorHintInverse">@android:color/hint_foreground_light</item> <item name="textColorSearchUrl">@android:color/search_url_text</item> + <item name="textColorHighlight">@android:color/highlighted_text_dark</item> + <item name="textColorHighlightInverse">@android:color/highlighted_text_light</item> + <item name="textColorLink">@android:color/link_text_dark</item> + <item name="textColorLinkInverse">@android:color/link_text_light</item> <item name="textAppearanceLarge">@android:style/TextAppearance.Large</item> <item name="textAppearanceMedium">@android:style/TextAppearance.Medium</item> @@ -233,7 +237,7 @@ <item name="colorBackground">@android:color/background_light</item> <item name="colorForeground">@color/bright_foreground_light</item> <item name="colorForegroundInverse">@android:color/bright_foreground_light_inverse</item> - + <item name="textColorPrimary">@android:color/primary_text_light</item> <item name="textColorSecondary">@android:color/secondary_text_light</item> <item name="textColorTertiary">@android:color/tertiary_text_light</item> @@ -247,7 +251,11 @@ <item name="textColorPrimaryInverseNoDisable">@android:color/primary_text_dark_nodisable</item> <item name="textColorSecondaryInverseNoDisable">@android:color/secondary_text_dark_nodisable</item> <item name="textColorHint">@android:color/hint_foreground_light</item> - <item name="textColorHintInverse">@android:color/hint_foreground_dark</item> + <item name="textColorHintInverse">@android:color/hint_foreground_dark</item> + <item name="textColorHighlight">@android:color/highlighted_text_light</item> + <item name="textColorHighlightInverse">@android:color/highlighted_text_dark</item> + <item name="textColorLink">@android:color/link_text_light</item> + <item name="textColorLinkInverse">@android:color/link_text_dark</item> <item name="popupWindowStyle">@android:style/Widget.PopupWindow</item> diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 320fc4d..5b8d62c 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -27,6 +27,7 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.FileNotFoundException; /** * Creates Bitmap objects from various sources, including files, streams, @@ -582,6 +583,139 @@ public class BitmapFactory { nativeSetDefaultConfig(config.nativeInt); } + /** + * Create a LargeBitmap from the specified byte array. + * Currently only the Jpeg format is supported. + * + * @param data byte array of compressed image data. + * @param offset offset into data for where the decoder should begin + * parsing. + * @param length the number of bytes, beginning at offset, to parse + * @param isShareable If this is true, then the LargeBitmap may keep a + * shallow reference to the input. If this is false, + * then the LargeBitmap will explicitly make a copy of the + * input data, and keep that. Even if sharing is allowed, + * the implementation may still decide to make a deep + * copy of the input data. If an image is progressively encoded, + * allowing sharing may degrade the decoding speed. + * @return LargeBitmap, or null if the image data could not be decoded. + * @throws IOException if the image format is not supported or can not be decoded. + * @hide + */ + public static LargeBitmap createLargeBitmap(byte[] data, + int offset, int length, boolean isShareable) throws IOException { + if ((offset | length) < 0 || data.length < offset + length) { + throw new ArrayIndexOutOfBoundsException(); + } + return nativeCreateLargeBitmap(data, offset, length, isShareable); + } + + /** + * Create a LargeBitmap from the file descriptor. + * The position within the descriptor will not be changed when + * this returns, so the descriptor can be used again as is. + * Currently only the Jpeg format is supported. + * + * @param fd The file descriptor containing the data to decode + * @param isShareable If this is true, then the LargeBitmap may keep a + * shallow reference to the input. If this is false, + * then the LargeBitmap will explicitly make a copy of the + * input data, and keep that. Even if sharing is allowed, + * the implementation may still decide to make a deep + * copy of the input data. If an image is progressively encoded, + * allowing sharing may degrade the decoding speed. + * @return LargeBitmap, or null if the image data could not be decoded. + * @throws IOException if the image format is not supported or can not be decoded. + * @hide + */ + public static LargeBitmap createLargeBitmap( + FileDescriptor fd, boolean isShareable) throws IOException { + if (MemoryFile.isMemoryFile(fd)) { + int mappedlength = MemoryFile.getSize(fd); + MemoryFile file = new MemoryFile(fd, mappedlength, "r"); + InputStream is = file.getInputStream(); + return createLargeBitmap(is, isShareable); + } + return nativeCreateLargeBitmap(fd, isShareable); + } + + /** + * Create a LargeBitmap from an input stream. + * The stream's position will be where ever it was after the encoded data + * was read. + * Currently only the Jpeg format is supported. + * + * @param is The input stream that holds the raw data to be decoded into a + * LargeBitmap. + * @param isShareable If this is true, then the LargeBitmap may keep a + * shallow reference to the input. If this is false, + * then the LargeBitmap will explicitly make a copy of the + * input data, and keep that. Even if sharing is allowed, + * the implementation may still decide to make a deep + * copy of the input data. If an image is progressively encoded, + * allowing sharing may degrade the decoding speed. + * @return LargeBitmap, or null if the image data could not be decoded. + * @throws IOException if the image format is not supported or can not be decoded. + * @hide + */ + public static LargeBitmap createLargeBitmap(InputStream is, + boolean isShareable) throws IOException { + // we need mark/reset to work properly in JNI + + if (!is.markSupported()) { + is = new BufferedInputStream(is, 16 * 1024); + } + + if (is instanceof AssetManager.AssetInputStream) { + return nativeCreateLargeBitmap( + ((AssetManager.AssetInputStream) is).getAssetInt(), + isShareable); + } else { + // pass some temp storage down to the native code. 1024 is made up, + // but should be large enough to avoid too many small calls back + // into is.read(...). + byte [] tempStorage = null; + tempStorage = new byte[16 * 1024]; + return nativeCreateLargeBitmap(is, tempStorage, isShareable); + } + } + + /** + * Create a LargeBitmap from a file path. + * Currently only the Jpeg format is supported. + * + * @param pathName complete path name for the file to be decoded. + * @param isShareable If this is true, then the LargeBitmap may keep a + * shallow reference to the input. If this is false, + * then the LargeBitmap will explicitly make a copy of the + * input data, and keep that. Even if sharing is allowed, + * the implementation may still decide to make a deep + * copy of the input data. If an image is progressively encoded, + * allowing sharing may degrade the decoding speed. + * @return LargeBitmap, or null if the image data could not be decoded. + * @throws IOException if the image format is not supported or can not be decoded. + * @hide + */ + public static LargeBitmap createLargeBitmap(String pathName, + boolean isShareable) throws FileNotFoundException, IOException { + LargeBitmap bm = null; + InputStream stream = null; + + try { + stream = new FileInputStream(pathName); + bm = createLargeBitmap(stream, isShareable); + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + // do nothing here + } + } + } + return bm; + } + private static native void nativeSetDefaultConfig(int nativeConfig); private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts); @@ -591,5 +725,14 @@ public class BitmapFactory { private static native Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts); private static native byte[] nativeScaleNinePatch(byte[] chunk, float scale, Rect pad); + + private static native LargeBitmap nativeCreateLargeBitmap( + byte[] data, int offset, int length, boolean isShareable); + private static native LargeBitmap nativeCreateLargeBitmap( + FileDescriptor fd, boolean isShareable); + private static native LargeBitmap nativeCreateLargeBitmap( + InputStream is, byte[] storage, boolean isShareable); + private static native LargeBitmap nativeCreateLargeBitmap( + int asset, boolean isShareable); } diff --git a/graphics/java/android/graphics/LargeBitmap.java b/graphics/java/android/graphics/LargeBitmap.java new file mode 100644 index 0000000..6656b17 --- /dev/null +++ b/graphics/java/android/graphics/LargeBitmap.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DisplayMetrics; + +import java.io.OutputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +/** + * LargeBitmap can be used to decode a rectangle region from an image. + * LargeBimap is particularly useful when an original image is large and + * you only need parts of the image. + * + * To create a LargeBitmap, call BitmapFactory.createLargeBitmap(). + * Given a LargeBitmap, users can call decodeRegion() repeatedly + * to get a decoded Bitmap of the specified region. + * @hide + */ +public final class LargeBitmap { + private int mNativeLargeBitmap; + private boolean mRecycled; + + /* Private constructor that must received an already allocated native + large bitmap int (pointer). + + This can be called from JNI code. + */ + private LargeBitmap(int lbm) { + mNativeLargeBitmap = lbm; + mRecycled = false; + } + + /** + * Decodes a rectangle region in the image specified by rect. + * + * @param rect The rectangle that specified the region to be decode. + * @param opts null-ok; Options that control downsampling. + * inPurgeable is not supported. + * @return The decoded bitmap, or null if the image data could not be + * decoded. + */ + public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) { + checkRecycled("decodeRegion called on recycled large bitmap"); + if (rect.left < 0 || rect.top < 0 || rect.right > getWidth() || rect.bottom > getHeight()) + throw new IllegalArgumentException("rectangle is not inside the image"); + return nativeDecodeRegion(mNativeLargeBitmap, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, options); + } + + /** Returns the original image's width */ + public int getWidth() { + checkRecycled("getWidth called on recycled large bitmap"); + return nativeGetWidth(mNativeLargeBitmap); + } + + /** Returns the original image's height */ + public int getHeight() { + checkRecycled("getHeight called on recycled large bitmap"); + return nativeGetHeight(mNativeLargeBitmap); + } + + /** + * Frees up the memory associated with this large bitmap, and mark the + * large bitmap as "dead", meaning it will throw an exception if decodeRegion(), + * getWidth() or getHeight() is called. + * This operation cannot be reversed, so it should only be called if you are + * sure there are no further uses for the large bitmap. This is an advanced call, + * and normally need not be called, since the normal GC process will free up this + * memory when there are no more references to this bitmap. + */ + public void recycle() { + if (!mRecycled) { + nativeClean(mNativeLargeBitmap); + mRecycled = true; + } + } + + /** + * Returns true if this large bitmap has been recycled. + * If so, then it is an error to try use its method. + * + * @return true if the large bitmap has been recycled + */ + public final boolean isRecycled() { + return mRecycled; + } + + /** + * Called by methods that want to throw an exception if the bitmap + * has already been recycled. + */ + private void checkRecycled(String errorMessage) { + if (mRecycled) { + throw new IllegalStateException(errorMessage); + } + } + + protected void finalize() { + recycle(); + } + + private static native Bitmap nativeDecodeRegion(int lbm, + int start_x, int start_y, int width, int height, + BitmapFactory.Options options); + private static native int nativeGetWidth(int lbm); + private static native int nativeGetHeight(int lbm); + private static native void nativeClean(int lbm); +} diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h index 2e1e8d8..be96935 100644 --- a/include/media/stagefright/MPEG4Writer.h +++ b/include/media/stagefright/MPEG4Writer.h @@ -128,6 +128,12 @@ private: // Write the first chunk from the given ChunkInfo. void writeFirstChunk(ChunkInfo* info); + // Adjust other track media clock (presumably wall clock) + // based on audio track media clock with the drift time. + int64_t mDriftTimeUs; + void addDriftTimeUs(int64_t driftTimeUs); + int64_t getDriftTimeUs(); + void lock(); void unlock(); diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h index ab1fa4f..43354c2 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -86,10 +86,10 @@ enum { // Track authoring progress status // kKeyTrackTimeStatus is used to track progress in elapsed time - // kKeyTrackFrameStatus is used to track progress in authored frames - kKeyTrackFrameStatus = 'tkfm', // int32_t kKeyTrackTimeStatus = 'tktm', // int64_t + kKeyNotRealTime = 'ntrt', // bool (int32_t) + }; enum { diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h index dab35b3..05cd96d 100644 --- a/include/ui/EventHub.h +++ b/include/ui/EventHub.h @@ -224,6 +224,7 @@ private: uint8_t* keyBitmask; KeyLayoutMap* layoutMap; String8 keylayoutFilename; + int fd; device_t* next; device_t(int32_t _id, const char* _path, const char* name); diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp index 74d50e4..264ad3d 100644 --- a/libs/hwui/Matrix.cpp +++ b/libs/hwui/Matrix.cpp @@ -260,10 +260,10 @@ void Matrix4::mapRect(Rect& r) const { } float vertices[] = { - r.left, r.top, - r.right, r.top, - r.right, r.bottom, - r.left, r.bottom + r.left, r.top, + r.right, r.top, + r.right, r.bottom, + r.left, r.bottom }; float x, y, z; @@ -297,8 +297,8 @@ void Matrix4::mapRect(Rect& r) const { void Matrix4::dump() const { LOGD("Matrix4[simple=%d", mSimpleMatrix); - LOGD(" %f %f %f %f", data[kScaleX], data[kSkewX], data[ 8], data[kTranslateX]); - LOGD(" %f %f %f %f", data[kSkewY], data[kScaleY], data[ 9], data[kTranslateY]); + LOGD(" %f %f %f %f", data[kScaleX], data[kSkewX], data[8], data[kTranslateX]); + LOGD(" %f %f %f %f", data[kSkewY], data[kScaleY], data[9], data[kTranslateY]); LOGD(" %f %f %f %f", data[2], data[6], data[kScaleZ], data[kTranslateZ]); LOGD(" %f %f %f %f", data[kPerspective0], data[kPerspective1], data[11], data[kPerspective2]); LOGD("]"); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index da5b9dd..35e17bf 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -183,17 +183,17 @@ OpenGLRenderer::~OpenGLRenderer() { void OpenGLRenderer::setViewport(int width, int height) { glViewport(0, 0, width, height); - mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1); mWidth = width; mHeight = height; mFirstSnapshot->height = height; + mFirstSnapshot->viewport.set(0, 0, width, height); } void OpenGLRenderer::prepare() { mSnapshot = new Snapshot(mFirstSnapshot); - mSaveCount = 0; + mSaveCount = 1; glDisable(GL_SCISSOR_TEST); @@ -219,19 +219,17 @@ int OpenGLRenderer::save(int flags) { } void OpenGLRenderer::restore() { - if (mSaveCount == 0) return; - - if (restoreSnapshot()) { + if (mSaveCount > 1 && restoreSnapshot()) { setScissorFromClip(); } } void OpenGLRenderer::restoreToCount(int saveCount) { - if (saveCount <= 0 || saveCount > mSaveCount) return; + if (saveCount < 1) saveCount = 1; bool restoreClip = false; - while (mSaveCount != saveCount - 1) { + while (mSaveCount > saveCount) { restoreClip |= restoreSnapshot(); } @@ -242,7 +240,7 @@ void OpenGLRenderer::restoreToCount(int saveCount) { int OpenGLRenderer::saveSnapshot() { mSnapshot = new Snapshot(mSnapshot); - return ++mSaveCount; + return mSaveCount++; } bool OpenGLRenderer::restoreSnapshot() { @@ -254,6 +252,8 @@ bool OpenGLRenderer::restoreSnapshot() { sp<Snapshot> previous = mSnapshot->previous; if (restoreOrtho) { + Rect& r = previous->viewport; + glViewport(r.left, r.top, r.right, r.bottom); mOrthoMatrix.load(current->orthoMatrix); } @@ -261,42 +261,17 @@ bool OpenGLRenderer::restoreSnapshot() { composeLayer(current, previous); } - mSnapshot = previous; - mSaveCount--; - - return restoreClip; -} - -void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { - if (!current->layer) { - LOGE("Attempting to compose a layer that does not exist"); - return; + bool skip = mSnapshot->skip; + if (!skip) { + mSaveCount--; } + mSnapshot = previous; - // Unbind current FBO and restore previous one - // Most of the time, previous->fbo will be 0 to bind the default buffer - glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo); - - // Restore the clip from the previous snapshot - const Rect& clip = previous->clipRect; - glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight()); - - Layer* layer = current->layer; - const Rect& rect = layer->layer; - - drawTextureRect(rect.left, rect.top, rect.right, rect.bottom, - layer->texture, layer->alpha, layer->mode, layer->blend); - - LayerSize size(rect.getWidth(), rect.getHeight()); - // Failing to add the layer to the cache should happen only if the - // layer is too large - if (!mLayerCache.put(size, layer)) { - LAYER_LOGD("Deleting layer"); - - glDeleteFramebuffers(1, &layer->fbo); - glDeleteTextures(1, &layer->texture); - - delete layer; + if (!skip) { + return restoreClip; + } else { + bool restorePreviousClip = restoreSnapshot(); + return restoreClip || restorePreviousClip; } } @@ -355,32 +330,71 @@ bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top, glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); - // Save the layer in the snapshot - snapshot->flags |= Snapshot::kFlagIsLayer; layer->mode = mode; layer->alpha = alpha / 255.0f; layer->layer.set(left, top, right, bottom); + // Save the layer in the snapshot + snapshot->flags |= Snapshot::kFlagIsLayer; snapshot->layer = layer; snapshot->fbo = layer->fbo; // Creates a new snapshot to draw into the FBO saveSnapshot(); - // TODO: This doesn't preserve other transformations (check Skia first) + mSaveCount--; + + mSnapshot->skip = true; mSnapshot->transform.loadTranslate(-left, -top, 0.0f); mSnapshot->setClip(0.0f, 0.0f, right - left, bottom - top); + mSnapshot->viewport.set(0.0f, 0.0f, right - left, bottom - top); mSnapshot->height = bottom - top; + setScissorFromClip(); mSnapshot->flags = Snapshot::kFlagDirtyOrtho | Snapshot::kFlagClipSet; mSnapshot->orthoMatrix.load(mOrthoMatrix); // Change the ortho projection - mOrthoMatrix.loadOrtho(0.0f, right - left, bottom - top, 0.0f, 0.0f, 1.0f); + glViewport(0, 0, right - left, bottom - top); + // Don't flip the FBO, it will get flipped when drawing back to the framebuffer + mOrthoMatrix.loadOrtho(0.0f, right - left, 0.0f, bottom - top, -1.0f, 1.0f); return true; } +void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { + if (!current->layer) { + LOGE("Attempting to compose a layer that does not exist"); + return; + } + + // Unbind current FBO and restore previous one + // Most of the time, previous->fbo will be 0 to bind the default buffer + glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo); + + // Restore the clip from the previous snapshot + const Rect& clip = previous->clipRect; + glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight()); + + Layer* layer = current->layer; + const Rect& rect = layer->layer; + + drawTextureRect(rect.left, rect.top, rect.right, rect.bottom, + layer->texture, layer->alpha, layer->mode, layer->blend); + + LayerSize size(rect.getWidth(), rect.getHeight()); + // Failing to add the layer to the cache should happen only if the + // layer is too large + if (!mLayerCache.put(size, layer)) { + LAYER_LOGD("Deleting layer"); + + glDeleteFramebuffers(1, &layer->fbo); + glDeleteTextures(1, &layer->texture); + + delete layer; + } +} + /////////////////////////////////////////////////////////////////////////////// // Transforms /////////////////////////////////////////////////////////////////////////////// @@ -424,14 +438,8 @@ const Rect& OpenGLRenderer::getClipBounds() { } bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) { - SkRect sr; - sr.set(left, top, right, bottom); - - SkMatrix m; - mSnapshot->transform.copyTo(m); - m.mapRect(&sr); - - Rect r(sr.fLeft, sr.fTop, sr.fRight, sr.fBottom); + Rect r(left, top, right, bottom); + mSnapshot->transform.mapRect(r); return !mSnapshot->clipRect.intersects(r); } @@ -562,82 +570,6 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, drawColorRect(left, top, right, bottom, color, mode); } -void OpenGLRenderer::renderShadow(const ShadowTexture* texture, float x, float y, - SkXfermode::Mode mode) { - const float sx = x - texture->left + mShadowDx; - const float sy = y - texture->top + mShadowDy; - - const GLfloat a = ((mShadowColor >> 24) & 0xFF) / 255.0f; - const GLfloat r = a * ((mShadowColor >> 16) & 0xFF) / 255.0f; - const GLfloat g = a * ((mShadowColor >> 8) & 0xFF) / 255.0f; - const GLfloat b = a * ((mShadowColor ) & 0xFF) / 255.0f; - - GLuint textureUnit = 0; - renderTextureAlpha8(texture, textureUnit, sx, sy, r, g, b, a, mode, false); -} - -void OpenGLRenderer::renderTextureAlpha8(const Texture* texture, GLuint& textureUnit, - float x, float y, float r, float g, float b, float a, SkXfermode::Mode mode, - bool applyFilters) { - // Describe the required shaders - ProgramDescription description; - description.hasTexture = true; - description.hasAlpha8Texture = true; - - if (applyFilters) { - if (mShader) { - mShader->describe(description, mExtensions); - } - if (mColorFilter) { - mColorFilter->describe(description, mExtensions); - } - } - - // Build and use the appropriate shader - useProgram(mProgramCache.get(description)); - - // Setup the blending mode - chooseBlending(true, mode); - bindTexture(texture->id, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit); - glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit); - - int texCoordsSlot = mCurrentProgram->getAttrib("texCoords"); - glEnableVertexAttribArray(texCoordsSlot); - - // Setup attributes - glVertexAttribPointer(mCurrentProgram->position, 2, GL_FLOAT, GL_FALSE, - gMeshStride, &mMeshVertices[0].position[0]); - glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE, - gMeshStride, &mMeshVertices[0].texture[0]); - - // Setup uniforms - mModelView.loadTranslate(x, y, 0.0f); - mModelView.scale(texture->width, texture->height, 1.0f); - mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform); - - glUniform4f(mCurrentProgram->color, r, g, b, a); - - textureUnit++; - if (applyFilters) { - // Setup attributes and uniforms required by the shaders - if (mShader) { - mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit); - } - if (mColorFilter) { - mColorFilter->setupProgram(mCurrentProgram); - } - } - - // Draw the mesh - glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); - - glDisableVertexAttribArray(texCoordsSlot); -} - -#define kStdStrikeThru_Offset (-6.0f / 21.0f) -#define kStdUnderline_Offset (1.0f / 9.0f) -#define kStdUnderline_Thickness (1.0f / 18.0f) - void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint) { if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) { @@ -668,7 +600,12 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, const ShadowTexture* shadow = mDropShadowCache.get(paint, text, bytesCount, count, mShadowRadius); const AutoTexture autoCleanup(shadow); - renderShadow(shadow, x, y, mode); + + setupShadow(shadow, x, y, mode); + + // Draw the mesh + glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); + glDisableVertexAttribArray(mCurrentProgram->getAttrib("texCoords")); } uint32_t color = paint->getColor(); @@ -677,94 +614,19 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, const GLfloat g = a * ((color >> 8) & 0xFF) / 255.0f; const GLfloat b = a * ((color ) & 0xFF) / 255.0f; - mModelView.loadIdentity(); - GLuint textureUnit = 0; - // Needs to be set prior to calling FontRenderer::getTexture() glActiveTexture(gTextureUnits[textureUnit]); - ProgramDescription description; - description.hasTexture = true; - description.hasAlpha8Texture = true; - if (mShader) { - mShader->describe(description, mExtensions); - } - if (mColorFilter) { - mColorFilter->describe(description, mExtensions); - } - - useProgram(mProgramCache.get(description)); - mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform); - - // Text is always blended, no need to check the shader - chooseBlending(true, mode); - bindTexture(mFontRenderer.getTexture(), GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit); - glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit); - - int texCoordsSlot = mCurrentProgram->getAttrib("texCoords"); - glEnableVertexAttribArray(texCoordsSlot); - - // Always premultiplied - glUniform4f(mCurrentProgram->color, r, g, b, a); - - textureUnit++; - // Setup attributes and uniforms required by the shaders - if (mShader) { - mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit); - } - if (mColorFilter) { - mColorFilter->setupProgram(mCurrentProgram); - } + setupTextureAlpha8(mFontRenderer.getTexture(), 0, 0, textureUnit, x, y, r, g, b, a, + mode, false, true); const Rect& clip = mSnapshot->getLocalClip(); mFontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - glDisableVertexAttribArray(texCoordsSlot); - - // Handle underline and strike-through - uint32_t flags = paint->getFlags(); - if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { - float underlineWidth = length; - // If length is > 0.0f, we already measured the text for the text alignment - if (length <= 0.0f) { - underlineWidth = paint->measureText(text, bytesCount); - } - - float offsetX = 0; - switch (paint->getTextAlign()) { - case SkPaint::kCenter_Align: - offsetX = underlineWidth * 0.5f; - break; - case SkPaint::kRight_Align: - offsetX = underlineWidth; - break; - default: - break; - } - - if (underlineWidth > 0.0f) { - float textSize = paint->getTextSize(); - float height = textSize * kStdUnderline_Thickness; - - float left = x - offsetX; - float top = 0.0f; - float right = left + underlineWidth; - float bottom = 0.0f; - - if (flags & SkPaint::kUnderlineText_Flag) { - top = y + textSize * kStdUnderline_Offset; - bottom = top + height; - drawRect(left, top, right, bottom, paint); - } + glDisableVertexAttribArray(mCurrentProgram->getAttrib("texCoords")); - if (flags & SkPaint::kStrikeThruText_Flag) { - top = y + textSize * kStdStrikeThru_Offset; - bottom = top + height; - drawRect(left, top, right, bottom, paint); - } - } - } + drawTextDecorations(text, bytesCount, length, x, y, paint); } void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) { @@ -787,7 +649,12 @@ void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) { const float x = texture->left - texture->offset; const float y = texture->top - texture->offset; - renderTextureAlpha8(texture, textureUnit, x, y, r, g, b, a, mode, true); + + setupTextureAlpha8(texture, textureUnit, x, y, r, g, b, a, mode, true, true); + + // Draw the mesh + glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount); + glDisableVertexAttribArray(mCurrentProgram->getAttrib("texCoords")); } /////////////////////////////////////////////////////////////////////////////// @@ -837,6 +704,134 @@ void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) { // Drawing implementation /////////////////////////////////////////////////////////////////////////////// +void OpenGLRenderer::setupShadow(const ShadowTexture* texture, float x, float y, + SkXfermode::Mode mode) { + const float sx = x - texture->left + mShadowDx; + const float sy = y - texture->top + mShadowDy; + + const GLfloat a = ((mShadowColor >> 24) & 0xFF) / 255.0f; + const GLfloat r = a * ((mShadowColor >> 16) & 0xFF) / 255.0f; + const GLfloat g = a * ((mShadowColor >> 8) & 0xFF) / 255.0f; + const GLfloat b = a * ((mShadowColor ) & 0xFF) / 255.0f; + + GLuint textureUnit = 0; + setupTextureAlpha8(texture, textureUnit, sx, sy, r, g, b, a, mode, true, false); +} + +void OpenGLRenderer::setupTextureAlpha8(const Texture* texture, GLuint& textureUnit, + float x, float y, float r, float g, float b, float a, SkXfermode::Mode mode, + bool transforms, bool applyFilters) { + setupTextureAlpha8(texture->id, texture->width, texture->height, textureUnit, + x, y, r, g, b, a, mode, transforms, applyFilters); +} + +void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t height, + GLuint& textureUnit, float x, float y, float r, float g, float b, float a, + SkXfermode::Mode mode, bool transforms, bool applyFilters) { + // Describe the required shaders + ProgramDescription description; + description.hasTexture = true; + description.hasAlpha8Texture = true; + + if (applyFilters) { + if (mShader) { + mShader->describe(description, mExtensions); + } + if (mColorFilter) { + mColorFilter->describe(description, mExtensions); + } + } + + // Build and use the appropriate shader + useProgram(mProgramCache.get(description)); + + // Setup the blending mode + chooseBlending(true, mode); + bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit); + glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit); + + int texCoordsSlot = mCurrentProgram->getAttrib("texCoords"); + glEnableVertexAttribArray(texCoordsSlot); + + // Setup attributes + glVertexAttribPointer(mCurrentProgram->position, 2, GL_FLOAT, GL_FALSE, + gMeshStride, &mMeshVertices[0].position[0]); + glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE, + gMeshStride, &mMeshVertices[0].texture[0]); + + // Setup uniforms + if (transforms) { + mModelView.loadTranslate(x, y, 0.0f); + mModelView.scale(width, height, 1.0f); + } else { + mModelView.loadIdentity(); + } + mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform); + glUniform4f(mCurrentProgram->color, r, g, b, a); + + textureUnit++; + if (applyFilters) { + // Setup attributes and uniforms required by the shaders + if (mShader) { + mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit); + } + if (mColorFilter) { + mColorFilter->setupProgram(mCurrentProgram); + } + } +} + +#define kStdStrikeThru_Offset (-6.0f / 21.0f) +#define kStdUnderline_Offset (1.0f / 9.0f) +#define kStdUnderline_Thickness (1.0f / 18.0f) + +void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float length, + float x, float y, SkPaint* paint) { + // Handle underline and strike-through + uint32_t flags = paint->getFlags(); + if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { + float underlineWidth = length; + // If length is > 0.0f, we already measured the text for the text alignment + if (length <= 0.0f) { + underlineWidth = paint->measureText(text, bytesCount); + } + + float offsetX = 0; + switch (paint->getTextAlign()) { + case SkPaint::kCenter_Align: + offsetX = underlineWidth * 0.5f; + break; + case SkPaint::kRight_Align: + offsetX = underlineWidth; + break; + default: + break; + } + + if (underlineWidth > 0.0f) { + float textSize = paint->getTextSize(); + float height = textSize * kStdUnderline_Thickness; + + float left = x - offsetX; + float top = 0.0f; + float right = left + underlineWidth; + float bottom = 0.0f; + + if (flags & SkPaint::kUnderlineText_Flag) { + top = y + textSize * kStdUnderline_Offset; + bottom = top + height; + drawRect(left, top, right, bottom, paint); + } + + if (flags & SkPaint::kStrikeThruText_Flag) { + top = y + textSize * kStdStrikeThru_Offset; + bottom = top + height; + drawRect(left, top, right, bottom, paint); + } + } + } +} + void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom, int color, SkXfermode::Mode mode, bool ignoreTransform) { // If a shader is set, preserve only the alpha diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 948ff13..49143a5 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -226,17 +226,17 @@ private: GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0); /** - * Renders the specified shadow. + * Prepares the renderer to draw the specified shadow. * * @param texture The shadow texture * @param x The x coordinate of the shadow * @param y The y coordinate of the shadow * @param mode The blending mode */ - void renderShadow(const ShadowTexture* texture, float x, float y, SkXfermode::Mode mode); + void setupShadow(const ShadowTexture* texture, float x, float y, SkXfermode::Mode mode); /** - * Renders the specified Alpha8 texture as a rectangle. + * Prepares the renderer to draw the specified Alpha8 texture as a rectangle. * * @param texture The texture to render with * @param textureUnit The texture unit to use, may be modified @@ -247,11 +247,50 @@ private: * @param b The blue component of the color * @param a The alpha component of the color * @param mode The blending mode + * @param transforms True if the matrix passed to the shader should be multiplied + * by the model-view matrix * @param applyFilters Whether or not to take color filters and * shaders into account */ - void renderTextureAlpha8(const Texture* texture, GLuint& textureUnit, float x, float y, - float r, float g, float b, float a, SkXfermode::Mode mode, bool applyFilters); + void setupTextureAlpha8(const Texture* texture, GLuint& textureUnit, float x, float y, + float r, float g, float b, float a, SkXfermode::Mode mode, bool transforms, + bool applyFilters); + + /** + * Prepares the renderer to draw the specified Alpha8 texture as a rectangle. + * + * @param texture The texture to render with + * @param width The width of the texture + * @param height The height of the texture + * @param textureUnit The texture unit to use, may be modified + * @param x The x coordinate of the rectangle to draw + * @param y The y coordinate of the rectangle to draw + * @param r The red component of the color + * @param g The green component of the color + * @param b The blue component of the color + * @param a The alpha component of the color + * @param mode The blending mode + * @param transforms True if the matrix passed to the shader should be multiplied + * by the model-view matrix + * @param applyFilters Whether or not to take color filters and + * shaders into account + */ + void setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t height, + GLuint& textureUnit, float x, float y, float r, float g, float b, float a, + SkXfermode::Mode mode, bool transforms, bool applyFilters); + + /** + * Draws text underline and strike-through if needed. + * + * @param text The text to decor + * @param bytesCount The number of bytes in the text + * @param length The length in pixels of the text, can be <= 0.0f to force a measurement + * @param x The x coordinate where the text will be drawn + * @param y The y coordinate where the text will be drawn + * @param paint The paint to draw the text with + */ + void drawTextDecorations(const char* text, int bytesCount, float length, + float x, float y, SkPaint* paint); /** * Resets the texture coordinates stored in mMeshVertices. Setting the values diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index 9495bee..77650a3 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -42,7 +42,7 @@ namespace uirenderer { */ class Snapshot: public LightRefBase<Snapshot> { public: - Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0) { } + Snapshot(): skip(false), flags(0), previous(NULL), layer(NULL), fbo(0) { } /** * Copies the specified snapshot. Only the transform and clip rectangle @@ -54,10 +54,12 @@ public: height(s->height), transform(s->transform), clipRect(s->clipRect), + skip(false), flags(0), previous(s), layer(NULL), - fbo(s->fbo) { + fbo(s->fbo), + viewport(s->viewport) { if ((s->flags & Snapshot::kFlagClipSet) && !(s->flags & Snapshot::kFlagDirtyLocalClip)) { localClip.set(s->localClip); @@ -164,6 +166,12 @@ public: Rect clipRect; /** + * This snapshot should be skipped. Snapshots marked as skipped are + * created by the renderer and should be hidden from the user. + */ + bool skip; + + /** * Dirty flags. */ int flags; @@ -180,6 +188,11 @@ public: GLuint fbo; /** + * Current viewport. + */ + Rect viewport; + + /** * Contains the previous ortho matrix. */ mat4 orthoMatrix; diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h index 9c86187..5fbe9e5 100644 --- a/libs/hwui/TextDropShadowCache.h +++ b/libs/hwui/TextDropShadowCache.h @@ -47,7 +47,7 @@ struct ShadowText { ShadowText(const ShadowText& shadow): paint(shadow.paint), radius(shadow.radius), len(shadow.len), hash(shadow.hash) { - text = new char[len]; + text = new char[shadow.len]; memcpy(text, shadow.text, shadow.len); } diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp index 833bee0..1ef9c93 100644 --- a/libs/rs/rsFont.cpp +++ b/libs/rs/rsFont.cpp @@ -130,15 +130,7 @@ void Font::renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyp // Move to the next character in the array index = nextIndex; - CachedGlyphInfo *cachedGlyph = mCachedGlyphs.valueFor((uint32_t)utfChar); - - if(cachedGlyph == NULL) { - cachedGlyph = cacheGlyph((uint32_t)utfChar); - } - // Is the glyph still in texture cache? - if(!cachedGlyph->mIsValid) { - updateGlyphCache(cachedGlyph); - } + CachedGlyphInfo *cachedGlyph = getCachedUTFChar(utfChar); // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage if(cachedGlyph->mIsValid) { @@ -154,6 +146,20 @@ void Font::renderUTF(const char *text, uint32_t len, uint32_t start, int numGlyp } } +Font::CachedGlyphInfo* Font::getCachedUTFChar(int32_t utfChar) { + + CachedGlyphInfo *cachedGlyph = mCachedGlyphs.valueFor((uint32_t)utfChar); + if(cachedGlyph == NULL) { + cachedGlyph = cacheGlyph((uint32_t)utfChar); + } + // Is the glyph still in texture cache? + if(!cachedGlyph->mIsValid) { + updateGlyphCache(cachedGlyph); + } + + return cachedGlyph; +} + void Font::updateGlyphCache(CachedGlyphInfo *glyph) { FT_Error error = FT_Load_Glyph( mFace, glyph->mGlyphIndex, FT_LOAD_RENDER ); @@ -225,6 +231,7 @@ Font * Font::create(Context *rsc, const char *name, uint32_t fontSize, uint32_t bool isInitialized = newFont->init(name, fontSize, dpi); if(isInitialized) { activeFonts.push(newFont); + rsc->mStateFont.precacheLatin(newFont); return newFont; } @@ -422,6 +429,8 @@ void FontState::initTextTexture() nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(24, texType->getDimX(), nextLine, 0)); nextLine += mCacheLines.top()->mMaxHeight; + mCacheLines.push(new CacheTextureLine(24, texType->getDimX(), nextLine, 0)); + nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(32, texType->getDimX(), nextLine, 0)); nextLine += mCacheLines.top()->mMaxHeight; mCacheLines.push(new CacheTextureLine(32, texType->getDimX(), nextLine, 0)); @@ -611,12 +620,33 @@ void FontState::appendMeshQuad(float x1, float y1, float z1, } } +uint32_t FontState::getRemainingCacheCapacity() { + uint32_t remainingCapacity = 0; + float totalPixels = 0; + for(uint32_t i = 0; i < mCacheLines.size(); i ++) { + remainingCapacity += (mCacheLines[i]->mMaxWidth - mCacheLines[i]->mCurrentCol); + totalPixels += mCacheLines[i]->mMaxWidth; + } + remainingCapacity = (remainingCapacity * 100) / totalPixels; + return remainingCapacity; +} + +void FontState::precacheLatin(Font *font) { + // Remaining capacity is measured in % + uint32_t remainingCapacity = getRemainingCacheCapacity(); + uint32_t precacheIdx = 0; + while(remainingCapacity > 25 && precacheIdx < mLatinPrecache.size()) { + font->getCachedUTFChar((int32_t)mLatinPrecache[precacheIdx]); + remainingCapacity = getRemainingCacheCapacity(); + precacheIdx ++; + } +} + + void FontState::renderText(const char *text, uint32_t len, uint32_t startIndex, int numGlyphs, int x, int y) { checkInit(); - //String8 text8(text); - // Render code here Font *currentFont = mRSC->getFont(); if(!currentFont) { @@ -636,6 +666,12 @@ void FontState::renderText(const char *text, uint32_t len, uint32_t startIndex, issueDrawCommand(); mCurrentQuadIndex = 0; } + + // We store a string with letters in a rough frequency of occurrence + mLatinPrecache = String8(" eisarntolcdugpmhbyfvkwzxjq"); + mLatinPrecache += String8("EISARNTOLCDUGPMHBYFVKWZXJQ"); + mLatinPrecache += String8(",.?!()-+@;:`'"); + mLatinPrecache += String8("0123456789"); } void FontState::renderText(const char *text, int x, int y) diff --git a/libs/rs/rsFont.h b/libs/rs/rsFont.h index ab229be..defe38b 100644 --- a/libs/rs/rsFont.h +++ b/libs/rs/rsFont.h @@ -93,6 +93,7 @@ protected: bool mHasKerning; DefaultKeyedVector<uint32_t, CachedGlyphInfo* > mCachedGlyphs; + CachedGlyphInfo* getCachedUTFChar(int32_t utfChar); CachedGlyphInfo *cacheGlyph(uint32_t glyph); void updateGlyphCache(CachedGlyphInfo *glyph); @@ -129,9 +130,11 @@ protected: uint32_t mMaxWidth; uint32_t mCurrentRow; uint32_t mCurrentCol; + bool mDirty; CacheTextureLine(uint32_t maxHeight, uint32_t maxWidth, uint32_t currentRow, uint32_t currentCol) : - mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), mCurrentCol(currentCol) { + mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow), mCurrentCol(currentCol), + mDirty(false) { } bool fitBitmap(FT_Bitmap *bitmap, uint32_t *retOriginX, uint32_t *retOriginY) { @@ -143,6 +146,7 @@ protected: *retOriginX = mCurrentCol; *retOriginY = mCurrentRow; mCurrentCol += bitmap->width; + mDirty = true; return true; } @@ -151,6 +155,10 @@ protected: }; Vector<CacheTextureLine*> mCacheLines; + uint32_t getRemainingCacheCapacity(); + + void precacheLatin(Font *font); + String8 mLatinPrecache; Context *mRSC; diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp index 60de04a..aee4133 100644 --- a/libs/rs/rsProgramVertex.cpp +++ b/libs/rs/rsProgramVertex.cpp @@ -318,6 +318,12 @@ void ProgramVertex::setTextureMatrix(const rsc_Matrix *m) const mDirty = true; } +void ProgramVertex::getProjectionMatrix(rsc_Matrix *m) const +{ + float *f = static_cast<float *>(mConstants[0]->getPtr()); + memcpy(m, &f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], sizeof(rsc_Matrix)); +} + void ProgramVertex::transformToScreen(const Context *rsc, float *v4out, const float *v3in) const { float *f = static_cast<float *>(mConstants[0]->getPtr()); diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h index 1c8b9c8..a17c9ea 100644 --- a/libs/rs/rsProgramVertex.h +++ b/libs/rs/rsProgramVertex.h @@ -43,6 +43,7 @@ public: void addLight(const Light *); void setProjectionMatrix(const rsc_Matrix *) const; + void getProjectionMatrix(rsc_Matrix *) const; void setModelviewMatrix(const rsc_Matrix *) const; void setTextureMatrix(const rsc_Matrix *) const; diff --git a/libs/rs/rsScriptC_LibGL.cpp b/libs/rs/rsScriptC_LibGL.cpp index 4b8de76..a7877cd 100644 --- a/libs/rs/rsScriptC_LibGL.cpp +++ b/libs/rs/rsScriptC_LibGL.cpp @@ -117,6 +117,12 @@ static void SC_pfConstantColor(RsProgramFragment vpf, float r, float g, float b, pf->setConstantColor(r, g, b, a); } +static void SC_vpGetProjectionMatrix(rsc_Matrix *m) +{ + GET_TLS(); + rsc->getVertex()->getProjectionMatrix(m); +} + ////////////////////////////////////////////////////////////////////////////// // Drawing @@ -387,6 +393,8 @@ static ScriptCState::SymbolTable_t gSyms[] = { { "_Z31rsgProgramVertexLoadModelMatrixPK12rs_matrix4x4", (void *)&SC_vpLoadModelMatrix }, { "_Z33rsgProgramVertexLoadTextureMatrixPK12rs_matrix4x4", (void *)&SC_vpLoadTextureMatrix }, + { "_Z35rsgProgramVertexGetProjectionMatrixP12rs_matrix4x4", (void *)&SC_vpGetProjectionMatrix }, + { "_Z31rsgProgramFragmentConstantColor19rs_program_fragmentffff", (void *)&SC_pfConstantColor }, { "_Z11rsgGetWidthv", (void *)&SC_getWidth }, diff --git a/libs/rs/scriptc/rs_core.rsh b/libs/rs/scriptc/rs_core.rsh index aa9aebc..0bfb3b9 100644 --- a/libs/rs/scriptc/rs_core.rsh +++ b/libs/rs/scriptc/rs_core.rsh @@ -767,6 +767,92 @@ static void rsQuaternionGetMatrixUnit(rs_matrix4x4 *m, const rs_quaternion *q) { m->m[15] = 1.0f; } +///////////////////////////////////////////////////// +// utility funcs +///////////////////////////////////////////////////// +void __attribute__((overloadable)) +rsExtractFrustumPlanes(const rs_matrix4x4 *modelViewProj, + float4 *left, float4 *right, + float4 *top, float4 *bottom, + float4 *near, float4 *far) { + // x y z w = a b c d in the plane equation + left->x = modelViewProj->m[3] + modelViewProj->m[0]; + left->y = modelViewProj->m[7] + modelViewProj->m[4]; + left->z = modelViewProj->m[11] + modelViewProj->m[8]; + left->w = modelViewProj->m[15] + modelViewProj->m[12]; + + right->x = modelViewProj->m[3] - modelViewProj->m[0]; + right->y = modelViewProj->m[7] - modelViewProj->m[4]; + right->z = modelViewProj->m[11] - modelViewProj->m[8]; + right->w = modelViewProj->m[15] - modelViewProj->m[12]; + + top->x = modelViewProj->m[3] - modelViewProj->m[1]; + top->y = modelViewProj->m[7] - modelViewProj->m[5]; + top->z = modelViewProj->m[11] - modelViewProj->m[9]; + top->w = modelViewProj->m[15] - modelViewProj->m[13]; + + bottom->x = modelViewProj->m[3] + modelViewProj->m[1]; + bottom->y = modelViewProj->m[7] + modelViewProj->m[5]; + bottom->z = modelViewProj->m[11] + modelViewProj->m[9]; + bottom->w = modelViewProj->m[15] + modelViewProj->m[13]; + + near->x = modelViewProj->m[3] + modelViewProj->m[2]; + near->y = modelViewProj->m[7] + modelViewProj->m[6]; + near->z = modelViewProj->m[11] + modelViewProj->m[10]; + near->w = modelViewProj->m[15] + modelViewProj->m[14]; + + far->x = modelViewProj->m[3] - modelViewProj->m[2]; + far->y = modelViewProj->m[7] - modelViewProj->m[6]; + far->z = modelViewProj->m[11] - modelViewProj->m[10]; + far->w = modelViewProj->m[15] - modelViewProj->m[14]; + + float len = length(left->xyz); + *left /= len; + len = length(right->xyz); + *right /= len; + len = length(top->xyz); + *top /= len; + len = length(bottom->xyz); + *bottom /= len; + len = length(near->xyz); + *near /= len; + len = length(far->xyz); + *far /= len; +} + +bool __attribute__((overloadable)) +rsIsSphereInFrustum(float4 *sphere, + float4 *left, float4 *right, + float4 *top, float4 *bottom, + float4 *near, float4 *far) { + + float distToCenter = dot(left->xyz, sphere->xyz) + left->w; + if(distToCenter < -sphere->w) { + return false; + } + distToCenter = dot(right->xyz, sphere->xyz) + right->w; + if(distToCenter < -sphere->w) { + return false; + } + distToCenter = dot(top->xyz, sphere->xyz) + top->w; + if(distToCenter < -sphere->w) { + return false; + } + distToCenter = dot(bottom->xyz, sphere->xyz) + bottom->w; + if(distToCenter < -sphere->w) { + return false; + } + distToCenter = dot(near->xyz, sphere->xyz) + near->w; + if(distToCenter < -sphere->w) { + return false; + } + distToCenter = dot(far->xyz, sphere->xyz) + far->w; + if(distToCenter < -sphere->w) { + return false; + } + return true; +} + ///////////////////////////////////////////////////// // int ops diff --git a/libs/rs/scriptc/rs_graphics.rsh b/libs/rs/scriptc/rs_graphics.rsh index 63bd9d7..c0b2d2d 100644 --- a/libs/rs/scriptc/rs_graphics.rsh +++ b/libs/rs/scriptc/rs_graphics.rsh @@ -27,6 +27,9 @@ extern void __attribute__((overloadable)) rsgProgramVertexLoadTextureMatrix(const rs_matrix4x4 *); extern void __attribute__((overloadable)) + rsgProgramVertexGetProjectionMatrix(rs_matrix4x4 *); + +extern void __attribute__((overloadable)) rsgProgramFragmentConstantColor(rs_program_fragment, float, float, float, float); extern uint __attribute__((overloadable)) @@ -92,8 +95,6 @@ rsgMeshComputeBoundingBox(rs_mesh mesh, float3 *bBoxMin, float3 *bBoxMax) { bBoxMax->z = z2; } - - /////////////////////////////////////////////////////// // misc diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp index 124f7b3..b1284fe 100644 --- a/libs/ui/EventHub.cpp +++ b/libs/ui/EventHub.cpp @@ -60,7 +60,6 @@ #define ID_MASK 0x0000ffff #define SEQ_MASK 0x7fff0000 #define SEQ_SHIFT 16 -#define id_to_index(id) ((id&ID_MASK)+1) #ifndef ABS_MT_TOUCH_MAJOR #define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ @@ -87,7 +86,7 @@ static inline int max(int v1, int v2) EventHub::device_t::device_t(int32_t _id, const char* _path, const char* name) : id(_id), path(_path), name(name), classes(0) - , keyBitmask(NULL), layoutMap(new KeyLayoutMap()), next(NULL) { + , keyBitmask(NULL), layoutMap(new KeyLayoutMap()), fd(-1), next(NULL) { } EventHub::device_t::~device_t() { @@ -151,9 +150,9 @@ status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, struct input_absinfo info; - if(ioctl(mFDs[id_to_index(device->id)].fd, EVIOCGABS(axis), &info)) { + if(ioctl(device->fd, EVIOCGABS(axis), &info)) { LOGW("Error reading absolute controller %d for device %s fd %d\n", - axis, device->name.string(), mFDs[id_to_index(device->id)].fd); + axis, device->name.string(), device->fd); return -errno; } @@ -182,7 +181,7 @@ int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const { int32_t EventHub::getScanCodeStateLocked(device_t* device, int32_t scanCode) const { uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)]; memset(key_bitmask, 0, sizeof(key_bitmask)); - if (ioctl(mFDs[id_to_index(device->id)].fd, + if (ioctl(device->fd, EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { return test_bit(scanCode, key_bitmask) ? AKEY_STATE_DOWN : AKEY_STATE_UP; } @@ -205,8 +204,7 @@ int32_t EventHub::getKeyCodeStateLocked(device_t* device, int32_t keyCode) const uint8_t key_bitmask[sizeof_bit_array(KEY_MAX + 1)]; memset(key_bitmask, 0, sizeof(key_bitmask)); - if (ioctl(mFDs[id_to_index(device->id)].fd, - EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { + if (ioctl(device->fd, EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) { #if 0 for (size_t i=0; i<=KEY_MAX; i++) { LOGI("(Scan code %d: down=%d)", i, test_bit(i, key_bitmask)); @@ -242,7 +240,7 @@ int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const { int32_t EventHub::getSwitchStateLocked(device_t* device, int32_t sw) const { uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)]; memset(sw_bitmask, 0, sizeof(sw_bitmask)); - if (ioctl(mFDs[id_to_index(device->id)].fd, + if (ioctl(device->fd, EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) { return test_bit(sw, sw_bitmask) ? AKEY_STATE_DOWN : AKEY_STATE_UP; } @@ -638,6 +636,7 @@ int EventHub::open_device(const char *deviceName) return -1; } + device->fd = fd; mFDs[mFDCount].fd = fd; mFDs[mFDCount].events = POLLIN; diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index daa20a8..a616aae 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -731,7 +731,9 @@ sp<MediaSource> StagefrightRecorder::createAudioSource() { encMeta->setInt32(kKeyChannelCount, mAudioChannels); encMeta->setInt32(kKeySampleRate, mSampleRate); encMeta->setInt32(kKeyBitRate, mAudioBitRate); - encMeta->setInt32(kKeyTimeScale, mAudioTimeScale); + if (mAudioTimeScale > 0) { + encMeta->setInt32(kKeyTimeScale, mAudioTimeScale); + } OMXClient client; CHECK_EQ(client.connect(), OK); @@ -1032,7 +1034,9 @@ status_t StagefrightRecorder::setupVideoEncoder(sp<MediaSource> *source) { enc_meta->setInt32(kKeyStride, stride); enc_meta->setInt32(kKeySliceHeight, sliceHeight); enc_meta->setInt32(kKeyColorFormat, colorFormat); - enc_meta->setInt32(kKeyTimeScale, mVideoTimeScale); + if (mVideoTimeScale > 0) { + enc_meta->setInt32(kKeyTimeScale, mVideoTimeScale); + } if (mVideoEncoderProfile != -1) { enc_meta->setInt32(kKeyVideoProfile, mVideoEncoderProfile); } @@ -1115,7 +1119,9 @@ status_t StagefrightRecorder::startMPEG4Recording() { meta->setInt32(kKeyFileType, mOutputFormat); meta->setInt32(kKeyBitRate, totalBitRate); meta->setInt32(kKey64BitFileOffset, mUse64BitFileOffset); - meta->setInt32(kKeyTimeScale, mMovieTimeScale); + if (mMovieTimeScale > 0) { + meta->setInt32(kKeyTimeScale, mMovieTimeScale); + } if (mTrackEveryTimeDurationUs > 0) { meta->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs); } @@ -1191,9 +1197,9 @@ status_t StagefrightRecorder::reset() { mIFramesIntervalSec = 1; mAudioSourceNode = 0; mUse64BitFileOffset = false; - mMovieTimeScale = 1000; - mAudioTimeScale = 1000; - mVideoTimeScale = 1000; + mMovieTimeScale = -1; + mAudioTimeScale = -1; + mVideoTimeScale = -1; mCameraId = 0; mVideoEncoderProfile = -1; mVideoEncoderLevel = -1; diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index 1460f37..f52ec1a 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -72,6 +72,11 @@ private: bool mIsAudio; bool mIsMPEG4; int64_t mTrackDurationUs; + + // For realtime applications, we need to adjust the media clock + // for video track based on the audio media clock + bool mIsRealTimeRecording; + int64_t mMaxTimeStampUs; int64_t mEstimatedTrackSizeBytes; int64_t mMaxWriteTimeUs; int32_t mTimeScale; @@ -163,6 +168,12 @@ private: void getCodecSpecificDataFromInputFormatIfPossible(); + // Determine the track time scale + // If it is an audio track, try to use the sampling rate as + // the time scale; however, if user chooses the overwrite + // value, the user-supplied time scale will be used. + void setTimeScale(); + Track(const Track &); Track &operator=(const Track &); }; @@ -429,7 +440,7 @@ void MPEG4Writer::stop() { mMoovBoxBuffer = (uint8_t *) malloc(mEstimatedMoovBoxSize); mMoovBoxBufferOffset = 0; CHECK(mMoovBoxBuffer != NULL); - int32_t duration = (maxDurationUs * mTimeScale) / 1E6; + int32_t duration = (maxDurationUs * mTimeScale + 5E5) / 1E6; beginBox("moov"); @@ -744,10 +755,6 @@ MPEG4Writer::Track::Track( mReachedEOS(false) { getCodecSpecificDataFromInputFormatIfPossible(); - if (!mMeta->findInt32(kKeyTimeScale, &mTimeScale)) { - mTimeScale = 1000; - } - const char *mime; mMeta->findCString(kKeyMIMEType, &mime); mIsAvc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC); @@ -755,6 +762,28 @@ MPEG4Writer::Track::Track( mIsMPEG4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC); + setTimeScale(); +} + +void MPEG4Writer::Track::setTimeScale() { + LOGV("setTimeScale"); + // Default time scale + mTimeScale = 90000; + + if (mIsAudio) { + // Use the sampling rate as the default time scale for audio track. + int32_t sampleRate; + bool success = mMeta->findInt32(kKeySampleRate, &sampleRate); + CHECK(success); + mTimeScale = sampleRate; + } + + // If someone would like to overwrite the timescale, use user-supplied value. + int32_t timeScale; + if (mMeta->findInt32(kKeyTimeScale, &timeScale)) { + mTimeScale = timeScale; + } + CHECK(mTimeScale > 0); } @@ -940,6 +969,7 @@ status_t MPEG4Writer::startWriterThread() { mDone = false; mIsFirstChunk = true; + mDriftTimeUs = 0; for (List<Track *>::iterator it = mTracks.begin(); it != mTracks.end(); ++it) { ChunkInfo info; @@ -967,6 +997,14 @@ status_t MPEG4Writer::Track::start(MetaData *params) { startTimeUs = 0; } + mIsRealTimeRecording = true; + { + int32_t isNotRealTime; + if (params && params->findInt32(kKeyNotRealTime, &isNotRealTime)) { + mIsRealTimeRecording = (isNotRealTime == 0); + } + } + initTrackingProgressStatus(params); sp<MetaData> meta = new MetaData; @@ -1322,10 +1360,16 @@ void MPEG4Writer::Track::threadEntry() { int32_t nZeroLengthFrames = 0; int64_t lastTimestampUs = 0; // Previous sample time stamp in ms int64_t lastDurationUs = 0; // Between the previous two samples in ms + int64_t currDurationTicks = 0; // Timescale based ticks + int64_t lastDurationTicks = 0; // Timescale based ticks int32_t sampleCount = 1; // Sample count in the current stts table entry uint32_t previousSampleSize = 0; // Size of the previous sample int64_t previousPausedDurationUs = 0; int64_t timestampUs; + + int64_t wallClockTimeUs = 0; + int64_t lastWallClockTimeUs = 0; + sp<MetaData> meta_data; bool collectStats = collectStatisticalData(); @@ -1429,6 +1473,33 @@ void MPEG4Writer::Track::threadEntry() { } timestampUs -= previousPausedDurationUs; + if (mIsRealTimeRecording && !mIsAudio) { + // The minor adjustment on the timestamp is heuristic/experimental + // We are adjusting the timestamp to reduce the fluctuation of the duration + // of neighboring samples. This in turn helps reduce the track header size, + // especially, the number of entries in the "stts" box. + if (mNumSamples > 1) { + int64_t durationUs = timestampUs + mOwner->getDriftTimeUs() - lastTimestampUs; + int64_t diffUs = (durationUs > lastDurationUs) + ? durationUs - lastDurationUs + : lastDurationUs - durationUs; + if (diffUs <= 5000) { // XXX: Magic number 5ms + timestampUs = lastTimestampUs + lastDurationUs; + } else { + timestampUs += mOwner->getDriftTimeUs(); + } + } + } + CHECK(timestampUs >= 0); + if (mNumSamples > 1) { + if (timestampUs <= lastTimestampUs) { + LOGW("Drop a frame, since it arrives too late!"); + copy->release(); + copy = NULL; + continue; + } + } + LOGV("time stamp: %lld and previous paused duration %lld", timestampUs, previousPausedDurationUs); if (timestampUs > mTrackDurationUs) { @@ -1438,7 +1509,16 @@ void MPEG4Writer::Track::threadEntry() { mSampleSizes.push_back(sampleSize); ++mNumSamples; if (mNumSamples > 2) { - if (lastDurationUs != timestampUs - lastTimestampUs) { + // We need to use the time scale based ticks, rather than the + // timestamp itself to determine whether we have to use a new + // stts entry, since we may have rounding errors. + // The calculation is intended to reduce the accumulated + // rounding errors. + currDurationTicks = + ((timestampUs * mTimeScale + 500000LL) / 1000000LL - + (lastTimestampUs * mTimeScale + 500000LL) / 1000000LL); + + if (currDurationTicks != lastDurationTicks) { SttsTableEntry sttsEntry(sampleCount, lastDurationUs); mSttsTableEntries.push_back(sttsEntry); sampleCount = 1; @@ -1453,7 +1533,16 @@ void MPEG4Writer::Track::threadEntry() { previousSampleSize = sampleSize; } lastDurationUs = timestampUs - lastTimestampUs; + lastDurationTicks = currDurationTicks; lastTimestampUs = timestampUs; + if (mIsRealTimeRecording && mIsAudio) { + wallClockTimeUs = systemTime() / 1000; + int64_t wallClockDurationUs = wallClockTimeUs - lastWallClockTimeUs; + if (mNumSamples > 2) { + mOwner->addDriftTimeUs(lastDurationUs - wallClockDurationUs); + } + lastWallClockTimeUs = wallClockTimeUs; + } if (isSync != 0) { mStssTableEntries.push_back(mNumSamples); @@ -1679,6 +1768,18 @@ void MPEG4Writer::Track::logStatisticalData(bool isAudio) { } } +void MPEG4Writer::addDriftTimeUs(int64_t driftTimeUs) { + LOGV("addDriftTimeUs: %lld us", driftTimeUs); + Mutex::Autolock autolock(mLock); + mDriftTimeUs += driftTimeUs; +} + +int64_t MPEG4Writer::getDriftTimeUs() { + LOGV("getDriftTimeUs: %lld us", mDriftTimeUs); + Mutex::Autolock autolock(mLock); + return mDriftTimeUs; +} + void MPEG4Writer::Track::bufferChunk(int64_t timestampUs) { LOGV("bufferChunk"); @@ -1709,7 +1810,6 @@ void MPEG4Writer::Track::writeTrackHeader( LOGV("%s track time scale: %d", mIsAudio? "Audio": "Video", mTimeScale); - time_t now = time(NULL); int32_t mvhdTimeScale = mOwner->getTimeScale(); int64_t trakDurationUs = getDurationUs(); @@ -2024,10 +2124,18 @@ void MPEG4Writer::Track::writeTrackHeader( mOwner->beginBox("stts"); mOwner->writeInt32(0); // version=0, flags=0 mOwner->writeInt32(mSttsTableEntries.size()); + int64_t prevTimestampUs = 0; for (List<SttsTableEntry>::iterator it = mSttsTableEntries.begin(); it != mSttsTableEntries.end(); ++it) { mOwner->writeInt32(it->sampleCount); - int32_t dur = (it->sampleDurationUs * mTimeScale + 5E5) / 1E6; + + // Make sure that we are calculating the sample duration the exactly + // same way as we made decision on how to create stts entries. + int64_t currTimestampUs = prevTimestampUs + it->sampleDurationUs; + int32_t dur = ((currTimestampUs * mTimeScale + 500000LL) / 1000000LL - + (prevTimestampUs * mTimeScale + 500000LL) / 1000000LL); + prevTimestampUs += (it->sampleCount * it->sampleDurationUs); + mOwner->writeInt32(dur); } mOwner->endBox(); // stts diff --git a/packages/DefaultContainerService/res/values-zh-rCN/strings.xml b/packages/DefaultContainerService/res/values-zh-rCN/strings.xml index 982015c..65928b1 100644 --- a/packages/DefaultContainerService/res/values-zh-rCN/strings.xml +++ b/packages/DefaultContainerService/res/values-zh-rCN/strings.xml @@ -20,5 +20,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="service_name" msgid="4841491635055379553">"包裹访问帮助程序"</string> + <string name="service_name" msgid="4841491635055379553">"软件包访问帮助程序"</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java index 0aaa370..e7b0509 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java @@ -185,11 +185,11 @@ public abstract class Ticker { final Segment newSegment = new Segment(n, icon, n.notification.tickerText); // If there's already a notification schedule for this package and id, remove it. - for (int i=0; i<initialCount; i++) { + for (int i=0; i<mSegments.size(); i++) { Segment seg = mSegments.get(i); if (n.id == seg.notification.id && n.pkg.equals(seg.notification.pkg)) { // just update that one to use this new data instead - mSegments.remove(i); + mSegments.remove(i--); // restart iteration here } } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index a410fa4..6be5546 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -643,17 +643,16 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { boolean playSoundEffect = false; final PanelFeatureState st = getPanelState(featureId, true); - if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null) { - if (mActionBar.isOverflowReserved()) { - if (!mActionBar.isOverflowMenuShowing()) { - final Callback cb = getCallback(); - if (cb != null && - cb.onPreparePanel(featureId, st.createdPanelView, st.menu)) { - playSoundEffect = mActionBar.showOverflowMenu(); - } - } else { - playSoundEffect = mActionBar.hideOverflowMenu(); + if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null && + mActionBar.isOverflowReserved()) { + if (!mActionBar.isOverflowMenuShowing()) { + final Callback cb = getCallback(); + if (cb != null && + cb.onPreparePanel(featureId, st.createdPanelView, st.menu)) { + playSoundEffect = mActionBar.showOverflowMenu(); } + } else { + playSoundEffect = mActionBar.hideOverflowMenu(); } } else { if (st.isOpen || st.isHandled) { diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index 3059732..f825df9 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -425,7 +425,7 @@ class AppWidgetService extends IAppWidgetService.Stub } } - public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, RemoteViews views, int viewId) { + public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views) { if (appWidgetIds == null) { return; } @@ -437,7 +437,24 @@ class AppWidgetService extends IAppWidgetService.Stub synchronized (mAppWidgetIds) { for (int i=0; i<N; i++) { AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); - notifyAppWidgetViewDataChangedInstanceLocked(id, views, viewId); + updateAppWidgetInstanceLocked(id, views, true); + } + } + } + + public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) { + if (appWidgetIds == null) { + return; + } + if (appWidgetIds.length == 0) { + return; + } + final int N = appWidgetIds.length; + + synchronized (mAppWidgetIds) { + for (int i=0; i<N; i++) { + AppWidgetId id = lookupAppWidgetIdLocked(appWidgetIds[i]); + notifyAppWidgetViewDataChangedInstanceLocked(id, viewId); } } } @@ -459,11 +476,17 @@ class AppWidgetService extends IAppWidgetService.Stub } void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views) { + updateAppWidgetInstanceLocked(id, views, false); + } + + void updateAppWidgetInstanceLocked(AppWidgetId id, RemoteViews views, boolean isPartialUpdate) { // allow for stale appWidgetIds and other badness // lookup also checks that the calling process can access the appWidgetId // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - id.views = views; + + // We do not want to save this RemoteViews + if (!isPartialUpdate) id.views = views; // is anyone listening? if (id.host.callbacks != null) { @@ -479,18 +502,16 @@ class AppWidgetService extends IAppWidgetService.Stub } } - void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, RemoteViews views, int viewId) { + void notifyAppWidgetViewDataChangedInstanceLocked(AppWidgetId id, int viewId) { // allow for stale appWidgetIds and other badness // lookup also checks that the calling process can access the appWidgetId // drop unbound appWidgetIds (shouldn't be possible under normal circumstances) if (id != null && id.provider != null && !id.provider.zombie && !id.host.zombie) { - id.views = views; - // is anyone listening? if (id.host.callbacks != null) { try { // the lock is held, but this is a oneway call - id.host.callbacks.viewDataChanged(id.appWidgetId, views, viewId); + id.host.callbacks.viewDataChanged(id.appWidgetId, viewId); } catch (RemoteException e) { // It failed; remove the callback. No need to prune because // we know that this host is still referenced by this instance. diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 67796c6..9d262b6 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -130,11 +130,11 @@ public class NotificationManagerService extends INotificationManager.Stub private boolean mBatteryFull; private NotificationRecord mLedNotification; - private static final int BATTERY_LOW_ARGB = 0xFFFF0000; // Charging Low - red solid on - private static final int BATTERY_MEDIUM_ARGB = 0xFFFFFF00; // Charging - orange solid on - private static final int BATTERY_FULL_ARGB = 0xFF00FF00; // Charging Full - green solid on - private static final int BATTERY_BLINK_ON = 125; - private static final int BATTERY_BLINK_OFF = 2875; + private static int mBatteryLowARGB; + private static int mBatteryMediumARGB; + private static int mBatteryFullARGB; + private static int mBatteryLedOn; + private static int mBatteryLedOff; private static String idDebugString(Context baseContext, String packageName, int id) { Context c = null; @@ -453,6 +453,17 @@ public class NotificationManagerService extends INotificationManager.Stub mDefaultNotificationLedOff = resources.getInteger( com.android.internal.R.integer.config_defaultNotificationLedOff); + mBatteryLowARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLowARGB); + mBatteryMediumARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryMediumARGB); + mBatteryFullARGB = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryFullARGB); + mBatteryLedOn = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLedOn); + mBatteryLedOff = mContext.getResources().getInteger( + com.android.internal.R.integer.config_notificationsBatteryLedOff); + // Don't start allowing notifications until the setup wizard has run once. // After that, including subsequent boots, init with notifications turned on. // This works on the first boot because the setup wizard will toggle this @@ -1071,17 +1082,17 @@ public class NotificationManagerService extends INotificationManager.Stub // Battery low always shows, other states only show if charging. if (mBatteryLow) { if (mBatteryCharging) { - mBatteryLight.setColor(BATTERY_LOW_ARGB); + mBatteryLight.setColor(mBatteryLowARGB); } else { // Flash when battery is low and not charging - mBatteryLight.setFlashing(BATTERY_LOW_ARGB, LightsService.LIGHT_FLASH_TIMED, - BATTERY_BLINK_ON, BATTERY_BLINK_OFF); + mBatteryLight.setFlashing(mBatteryLowARGB, LightsService.LIGHT_FLASH_TIMED, + mBatteryLedOn, mBatteryLedOff); } } else if (mBatteryCharging) { if (mBatteryFull) { - mBatteryLight.setColor(BATTERY_FULL_ARGB); + mBatteryLight.setColor(mBatteryFullARGB); } else { - mBatteryLight.setColor(BATTERY_MEDIUM_ARGB); + mBatteryLight.setColor(mBatteryMediumARGB); } } else { mBatteryLight.turnOff(); diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 2fb481c..4ee89cc 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -151,6 +151,7 @@ class PowerManagerService extends IPowerManager.Stub static final int INITIAL_KEYBOARD_BRIGHTNESS = Power.BRIGHTNESS_OFF; private final int MY_UID; + private final int MY_PID; private boolean mDoneBooting = false; private boolean mBootCompleted = false; @@ -309,7 +310,7 @@ class PowerManagerService extends IPowerManager.Stub long ident = Binder.clearCallingIdentity(); try { PowerManagerService.this.acquireWakeLockLocked(mFlags, mToken, - MY_UID, mTag); + MY_UID, MY_PID, mTag); mHeld = true; } finally { Binder.restoreCallingIdentity(ident); @@ -434,11 +435,11 @@ class PowerManagerService extends IPowerManager.Stub } } - PowerManagerService() - { + PowerManagerService() { // Hack to get our uid... should have a func for this. long token = Binder.clearCallingIdentity(); - MY_UID = Binder.getCallingUid(); + MY_UID = Process.myUid(); + MY_PID = Process.myPid(); Binder.restoreCallingIdentity(token); // XXX remove this when the kernel doesn't timeout wake locks @@ -573,13 +574,13 @@ class PowerManagerService extends IPowerManager.Stub private class WakeLock implements IBinder.DeathRecipient { - WakeLock(int f, IBinder b, String t, int u) { + WakeLock(int f, IBinder b, String t, int u, int p) { super(); flags = f; binder = b; tag = t; uid = u == MY_UID ? Process.SYSTEM_UID : u; - pid = Binder.getCallingPid(); + pid = p; if (u != MY_UID || ( !"KEEP_SCREEN_ON_FLAG".equals(tag) && !"KeyInputQueue".equals(tag))) { @@ -631,21 +632,23 @@ class PowerManagerService extends IPowerManager.Stub public void acquireWakeLock(int flags, IBinder lock, String tag) { int uid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); if (uid != Process.myUid()) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); } long ident = Binder.clearCallingIdentity(); try { synchronized (mLocks) { - acquireWakeLockLocked(flags, lock, uid, tag); + acquireWakeLockLocked(flags, lock, uid, pid, tag); } } finally { Binder.restoreCallingIdentity(ident); } } - public void acquireWakeLockLocked(int flags, IBinder lock, int uid, String tag) { + public void acquireWakeLockLocked(int flags, IBinder lock, int uid, int pid, String tag) { int acquireUid = -1; + int acquirePid = -1; String acquireName = null; int acquireType = -1; @@ -657,7 +660,7 @@ class PowerManagerService extends IPowerManager.Stub WakeLock wl; boolean newlock; if (index < 0) { - wl = new WakeLock(flags, lock, tag, uid); + wl = new WakeLock(flags, lock, tag, uid, pid); switch (wl.flags & LOCK_MASK) { case PowerManager.FULL_WAKE_LOCK: @@ -730,13 +733,14 @@ class PowerManagerService extends IPowerManager.Stub } if (newlock) { acquireUid = wl.uid; + acquirePid = wl.pid; acquireName = wl.tag; acquireType = wl.monitorType; } if (acquireType >= 0) { try { - mBatteryStats.noteStartWakelock(acquireUid, acquireName, acquireType); + mBatteryStats.noteStartWakelock(acquireUid, acquirePid, acquireName, acquireType); } catch (RemoteException e) { // Ignore } @@ -756,6 +760,7 @@ class PowerManagerService extends IPowerManager.Stub private void releaseWakeLockLocked(IBinder lock, int flags, boolean death) { int releaseUid; + int releasePid; String releaseName; int releaseType; @@ -800,13 +805,14 @@ class PowerManagerService extends IPowerManager.Stub // Unlink the lock from the binder. wl.binder.unlinkToDeath(wl, 0); releaseUid = wl.uid; + releasePid = wl.pid; releaseName = wl.tag; releaseType = wl.monitorType; if (releaseType >= 0) { long origId = Binder.clearCallingIdentity(); try { - mBatteryStats.noteStopWakelock(releaseUid, releaseName, releaseType); + mBatteryStats.noteStopWakelock(releaseUid, releasePid, releaseName, releaseType); } catch (RemoteException e) { // Ignore } finally { diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java index 3f5888b..57f93c4 100644 --- a/services/java/com/android/server/WindowManagerService.java +++ b/services/java/com/android/server/WindowManagerService.java @@ -8085,12 +8085,12 @@ public class WindowManagerService extends IWindowManager.Stub if (oldHold != newHold) { try { if (oldHold != null) { - mBatteryStats.noteStopWakelock(oldHold.mUid, + mBatteryStats.noteStopWakelock(oldHold.mUid, -1, "window", BatteryStats.WAKE_TYPE_WINDOW); } if (newHold != null) { - mBatteryStats.noteStartWakelock(newHold.mUid, + mBatteryStats.noteStartWakelock(newHold.mUid, -1, "window", BatteryStats.WAKE_TYPE_WINDOW); } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index df930ad..55ec6aa 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -17,7 +17,6 @@ package com.android.server.am; import com.android.internal.R; -import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.os.BatteryStatsImpl; import com.android.server.AttributeCache; import com.android.server.IntentResolver; @@ -39,7 +38,6 @@ import android.app.AppGlobals; import android.app.ApplicationErrorReport; import android.app.Dialog; import android.app.IActivityController; -import android.app.IActivityManager; import android.app.IActivityWatcher; import android.app.IApplicationThread; import android.app.IInstrumentationWatcher; @@ -50,7 +48,6 @@ import android.app.Instrumentation; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.ResultInfo; import android.app.Service; import android.app.backup.IBackupManager; import android.content.ActivityNotFoundException; @@ -95,7 +92,6 @@ import android.os.Looper; import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; -import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -110,6 +106,7 @@ import android.util.Slog; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.SparseArray; +import android.util.TimeUtils; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -200,6 +197,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // The minimum amount of time between successive GC requests for a process. static final int GC_MIN_INTERVAL = 60*1000; + // The rate at which we check for apps using excessive wake locks -- 15 mins. + static final int WAKE_LOCK_CHECK_DELAY = 15*60*1000; + // How long we allow a receiver to run before giving up on it. static final int BROADCAST_TIMEOUT = 10*1000; @@ -771,6 +771,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen boolean mDidAppSwitch; /** + * Last time (in realtime) at which we checked for wake lock usage. + */ + long mLastWakeLockCheckTime; + + /** * Set while we are wanting to sleep, to prevent any * activities from being started/resumed. */ @@ -915,6 +920,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final int POST_HEAVY_NOTIFICATION_MSG = 24; static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25; static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26; + static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27; AlertDialog mUidAlert; @@ -1174,6 +1180,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } catch (RemoteException e) { } } break; + case CHECK_EXCESSIVE_WAKE_LOCKS_MSG: { + synchronized (ActivityManagerService.this) { + checkExcessiveWakeLocksLocked(true); + removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + if (mSleeping) { + Message nmsg = obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY); + } + } + } break; } } }; @@ -2560,6 +2576,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mProcDeaths[0]++; + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + stats.noteProcessDiedLocked(app.info.uid, pid); + } + // Clean up already done if the process has been re-started. if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) { @@ -3577,6 +3598,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { + // Start looking for apps that are abusing wake locks. + Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + mHandler.sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY); // Tell anyone interested that we are done booting! SystemProperties.set("sys.boot_completed", "1"); broadcastIntentLocked(null, null, @@ -5382,6 +5406,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else { Slog.w(TAG, "goingToSleep with no resumed activity!"); } + + // Initialize the wake times of all processes. + checkExcessiveWakeLocksLocked(false); + mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); + mHandler.sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY); } } @@ -5431,6 +5461,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mWindowManager.setEventDispatching(true); mSleeping = false; mMainStack.resumeTopActivityLocked(null); + mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG); } } @@ -11287,6 +11318,62 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + final void checkExcessiveWakeLocksLocked(boolean doKills) { + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + if (mLastWakeLockCheckTime == 0) { + doKills = false; + } + if (stats.isScreenOn()) { + doKills = false; + } + final long curRealtime = SystemClock.elapsedRealtime(); + final long timeSince = curRealtime - mLastWakeLockCheckTime; + mLastWakeLockCheckTime = curRealtime; + if (timeSince < (WAKE_LOCK_CHECK_DELAY/3)) { + doKills = false; + } + int i = mLruProcesses.size(); + while (i > 0) { + i--; + ProcessRecord app = mLruProcesses.get(i); + if (app.curAdj >= HIDDEN_APP_MIN_ADJ) { + long wtime; + synchronized (stats) { + wtime = stats.getProcessWakeTime(app.info.uid, + app.pid, curRealtime); + } + long timeUsed = wtime - app.lastWakeTime; + if (false) { + StringBuilder sb = new StringBuilder(128); + sb.append("Wake for "); + app.toShortString(sb); + sb.append(": over "); + TimeUtils.formatDuration(timeSince, sb); + sb.append(" used "); + TimeUtils.formatDuration(timeUsed, sb); + sb.append(" ("); + sb.append((timeUsed*100)/timeSince); + sb.append("%)"); + Slog.i(TAG, sb.toString()); + } + // If a process has held a wake lock for more + // than 50% of the time during this period, + // that sounds pad. Kill! + if (doKills && timeSince > 0 + && ((timeUsed*100)/timeSince) >= 50) { + Slog.i(TAG, "Excessive wake lock in " + app.processName + + " (pid " + app.pid + "): held " + timeUsed + + " during " + timeSince); + EventLog.writeEvent(EventLogTags.AM_KILL, app.pid, + app.processName, app.setAdj, "excessive wake lock"); + Process.killProcessQuiet(app.pid); + } else { + app.lastWakeTime = wtime; + } + } + } + } + private final boolean updateOomAdjLocked( ProcessRecord app, int hiddenAdj, ProcessRecord TOP_APP) { app.hiddenAdj = hiddenAdj; @@ -11309,6 +11396,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Likewise do a gc when an app is moving in to the // background (such as a service stopping). scheduleAppGcLocked(app); + // And note its current wake lock time. + BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); + synchronized (stats) { + app.lastWakeTime = stats.getProcessWakeTime(app.info.uid, + app.pid, SystemClock.elapsedRealtime()); + } } app.setRawAdj = app.curRawAdj; } diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 37da6f7..7314e04 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -93,31 +93,31 @@ public final class BatteryStatsService extends IBatteryStats.Stub { return data; } - public void noteStartWakelock(int uid, String name, int type) { + public void noteStartWakelock(int uid, int pid, String name, int type) { enforceCallingPermission(); synchronized (mStats) { - mStats.getUidStatsLocked(uid).noteStartWakeLocked(name, type); + mStats.noteStartWakeLocked(uid, pid, name, type); } } - public void noteStopWakelock(int uid, String name, int type) { + public void noteStopWakelock(int uid, int pid, String name, int type) { enforceCallingPermission(); synchronized (mStats) { - mStats.getUidStatsLocked(uid).noteStopWakeLocked(name, type); + mStats.noteStopWakeLocked(uid, pid, name, type); } } public void noteStartSensor(int uid, int sensor) { enforceCallingPermission(); synchronized (mStats) { - mStats.getUidStatsLocked(uid).noteStartSensor(sensor); + mStats.noteStartSensorLocked(uid, sensor); } } public void noteStopSensor(int uid, int sensor) { enforceCallingPermission(); synchronized (mStats) { - mStats.getUidStatsLocked(uid).noteStopSensor(sensor); + mStats.noteStopSensorLocked(uid, sensor); } } diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 18b1acb..6d1fbab 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -17,7 +17,6 @@ package com.android.server.am; import com.android.internal.os.BatteryStatsImpl; -import com.android.server.Watchdog; import android.app.ActivityManager; import android.app.Dialog; @@ -27,8 +26,9 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; +import android.os.SystemClock; import android.util.PrintWriterPrinter; +import android.util.TimeUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -74,6 +74,7 @@ class ProcessRecord { Bundle instrumentationArguments;// as given to us ComponentName instrumentationResultClass;// copy of instrumentationClass BroadcastRecord curReceiver;// receiver currently running in the app + long lastWakeTime; // How long proc held wake lock at last check long lastRequestedGc; // When we last asked the app to do a gc long lastLowMemory; // When we last told the app that memory is low boolean reportLowMemory; // Set to true when waiting to report low mem @@ -128,6 +129,8 @@ class ProcessRecord { ComponentName errorReportReceiver; void dump(PrintWriter pw, String prefix) { + final long now = SystemClock.uptimeMillis(); + if (info.className != null) { pw.print(prefix); pw.print("class="); pw.println(info.className); } @@ -157,8 +160,9 @@ class ProcessRecord { pw.print(" curReceiver="); pw.println(curReceiver); pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting="); pw.print(starting); pw.print(" lastPss="); pw.println(lastPss); - pw.print(prefix); pw.print("lastActivityTime="); pw.print(lastActivityTime); - pw.print(" lruWeight="); pw.println(lruWeight); + pw.print(prefix); pw.print("lastActivityTime="); + TimeUtils.formatDuration(lastActivityTime, now, pw); + pw.print(" lruWeight="); pw.print(lruWeight); pw.print(" hidden="); pw.print(hidden); pw.print(" empty="); pw.println(empty); pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj); @@ -177,6 +181,12 @@ class ProcessRecord { pw.print(" persistentActivities="); pw.println(persistentActivities); pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq); pw.print(" lruSeq="); pw.println(lruSeq); + pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime); + pw.print(" lastRequestedGc="); + TimeUtils.formatDuration(lastRequestedGc, now, pw); + pw.print(" lastLowMemory="); + TimeUtils.formatDuration(lastLowMemory, now, pw); + pw.print(" reportLowMemory="); pw.println(reportLowMemory); if (killedBackground) { pw.print(prefix); pw.print("killedBackground="); pw.println(killedBackground); } diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 75365ad..ab5a78d 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -31,6 +31,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.util.Slog; +import android.util.TimeUtils; import java.io.PrintWriter; import java.util.ArrayList; @@ -117,7 +118,10 @@ class ServiceRecord extends Binder { StartItem si = list.get(i); pw.print(prefix); pw.print("#"); pw.print(i); pw.print(" id="); pw.print(si.id); - if (now != 0) pw.print(" dur="); pw.print(now-si.deliveredTime); + if (now != 0) { + pw.print(" dur="); + TimeUtils.formatDuration(si.deliveredTime, now, pw); + } if (si.deliveryCount != 0) { pw.print(" dc="); pw.print(si.deliveryCount); } @@ -140,18 +144,26 @@ class ServiceRecord extends Binder { pw.print(prefix); pw.print("permission="); pw.println(permission); } long now = SystemClock.uptimeMillis(); - pw.print(prefix); pw.print("baseDir="); pw.print(baseDir); - if (!resDir.equals(baseDir)) pw.print(" resDir="); pw.print(resDir); - pw.print(" dataDir="); pw.println(dataDir); + long nowReal = SystemClock.elapsedRealtime(); + pw.print(prefix); pw.print("baseDir="); pw.println(baseDir); + if (!resDir.equals(baseDir)) pw.print(prefix); pw.print("resDir="); pw.println(resDir); + pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); if (isForeground || foregroundId != 0) { pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); pw.print(" foregroundId="); pw.print(foregroundId); pw.print(" foregroundNoti="); pw.println(foregroundNoti); } - pw.print(prefix); pw.print("lastActivity="); pw.print(lastActivity-now); - pw.print(" executingStart="); pw.print(executingStart-now); - pw.print(" restartTime="); pw.println(restartTime); + pw.print(prefix); pw.print("createTime="); + TimeUtils.formatDuration(createTime, nowReal, pw); + pw.print(" lastActivity="); + TimeUtils.formatDuration(lastActivity, now, pw); + pw.println(""); + pw.print(prefix); pw.print(" executingStart="); + TimeUtils.formatDuration(executingStart, now, pw); + pw.print(" restartTime="); + TimeUtils.formatDuration(restartTime, now, pw); + pw.println(""); if (startRequested || lastStartId != 0) { pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); pw.print(" stopIfKilled="); pw.print(stopIfKilled); @@ -162,13 +174,15 @@ class ServiceRecord extends Binder { || restartDelay != 0 || nextRestartTime != 0) { pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting); pw.print(" restartCount="); pw.print(restartCount); - pw.print(" restartDelay="); pw.print(restartDelay-now); - pw.print(" nextRestartTime="); pw.print(nextRestartTime-now); + pw.print(" restartDelay="); + TimeUtils.formatDuration(restartDelay, now, pw); + pw.print(" nextRestartTime="); + TimeUtils.formatDuration(nextRestartTime, now, pw); pw.print(" crashCount="); pw.println(crashCount); } if (deliveredStarts.size() > 0) { pw.print(prefix); pw.println("Delivered Starts:"); - dumpStartList(pw, prefix, deliveredStarts, SystemClock.uptimeMillis()); + dumpStartList(pw, prefix, deliveredStarts, now); } if (pendingStarts.size() > 0) { pw.print(prefix); pw.println("Pending Starts:"); @@ -213,7 +227,8 @@ class ServiceRecord extends Binder { dataDir = sInfo.applicationInfo.dataDir; exported = sInfo.exported; this.restarter = restarter; - createTime = lastActivity = SystemClock.uptimeMillis(); + createTime = SystemClock.elapsedRealtime(); + lastActivity = SystemClock.uptimeMillis(); } public AppBindRecord retrieveAppBindingLocked(Intent intent, diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index ea6aa94..c1165c7 100755 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -197,6 +197,9 @@ public class GpsLocationProvider implements LocationProviderInterface { // capabilities of the GPS engine private int mEngineCapabilities; + // true if XTRA is supported + private boolean mSupportsXtra; + // for calculating time to first fix private long mFixRequestTime = 0; // time to first fix for most recent session @@ -635,6 +638,7 @@ public class GpsLocationProvider implements LocationProviderInterface { mEnabled = native_init(); if (mEnabled) { + mSupportsXtra = native_supports_xtra(); if (mSuplServerHost != null) { native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); } @@ -839,7 +843,7 @@ public class GpsLocationProvider implements LocationProviderInterface { sendMessage(INJECT_NTP_TIME, 0, null); result = true; } else if ("force_xtra_injection".equals(command)) { - if (native_supports_xtra()) { + if (mSupportsXtra) { xtraDownloadRequest(); result = true; } @@ -1372,7 +1376,7 @@ public class GpsLocationProvider implements LocationProviderInterface { handleInjectNtpTime(); break; case DOWNLOAD_XTRA_DATA: - if (native_supports_xtra()) { + if (mSupportsXtra) { handleDownloadXtraData(); } break; diff --git a/services/jni/com_android_server_PowerManagerService.cpp b/services/jni/com_android_server_PowerManagerService.cpp index b80dbc5..146c177 100644 --- a/services/jni/com_android_server_PowerManagerService.cpp +++ b/services/jni/com_android_server_PowerManagerService.cpp @@ -22,6 +22,7 @@ #include "jni.h" #include <limits.h> #include <android_runtime/AndroidRuntime.h> +#include <utils/Timers.h> #include "com_android_server_PowerManagerService.h" namespace android { @@ -43,6 +44,11 @@ static Mutex gPowerManagerLock; static bool gScreenOn; static bool gScreenBright; +static nsecs_t gLastEventTime[POWER_MANAGER_LAST_EVENT + 1]; + +// Throttling interval for user activity calls. +static const nsecs_t MIN_TIME_BETWEEN_USERACTIVITIES = 500 * 1000000L; // 500ms + // ---------------------------------------------------------------------------- static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { @@ -67,6 +73,21 @@ bool android_server_PowerManagerService_isScreenBright() { void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) { if (gPowerManagerServiceObj) { + // Throttle calls into user activity by event type. + // We're a little conservative about argument checking here in case the caller + // passes in bad data which could corrupt system state. + if (eventType >= 0 && eventType <= POWER_MANAGER_LAST_EVENT) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + if (eventTime > now) { + eventTime = now; + } + + if (gLastEventTime[eventType] + MIN_TIME_BETWEEN_USERACTIVITIES > eventTime) { + return; + } + gLastEventTime[eventType] = eventTime; + } + JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod(gPowerManagerServiceObj, gPowerManagerServiceClassInfo.userActivity, @@ -136,6 +157,12 @@ int register_android_server_PowerManagerService(JNIEnv* env) { GET_METHOD_ID(gPowerManagerServiceClassInfo.userActivity, gPowerManagerServiceClassInfo.clazz, "userActivity", "(JZIZ)V"); + // Initialize + for (int i = 0; i < POWER_MANAGER_LAST_EVENT; i++) { + gLastEventTime[i] = LLONG_MIN; + } + gScreenOn = true; + gScreenBright = true; return 0; } diff --git a/services/jni/com_android_server_PowerManagerService.h b/services/jni/com_android_server_PowerManagerService.h index 9b05f38..7c329b2 100644 --- a/services/jni/com_android_server_PowerManagerService.h +++ b/services/jni/com_android_server_PowerManagerService.h @@ -30,6 +30,8 @@ enum { POWER_MANAGER_LONG_TOUCH_EVENT = 3, POWER_MANAGER_TOUCH_UP_EVENT = 4, POWER_MANAGER_BUTTON_EVENT = 5, // Button and trackball events. + + POWER_MANAGER_LAST_EVENT = POWER_MANAGER_BUTTON_EVENT, // Last valid event code. }; extern bool android_server_PowerManagerService_isScreenOn(); diff --git a/services/surfaceflinger/TextureManager.cpp b/services/surfaceflinger/TextureManager.cpp index 3b326df..0f448e0 100644 --- a/services/surfaceflinger/TextureManager.cpp +++ b/services/surfaceflinger/TextureManager.cpp @@ -190,7 +190,7 @@ status_t TextureManager::loadTexture(Texture* texture, return err; } - if (texture->target != GL_TEXTURE_2D) + if (texture->target != Texture::TEXTURE_2D) return INVALID_OPERATION; glBindTexture(GL_TEXTURE_2D, texture->name); diff --git a/telephony/java/android/telephony/gsm/SmsMessage.java b/telephony/java/android/telephony/gsm/SmsMessage.java index 37ef912..0c63c37 100644 --- a/telephony/java/android/telephony/gsm/SmsMessage.java +++ b/telephony/java/android/telephony/gsm/SmsMessage.java @@ -304,9 +304,9 @@ public class SmsMessage { int septets = GsmAlphabet.countGsmSeptets(messageBody, !use7bitOnly); ret[1] = septets; if (septets > MAX_USER_DATA_SEPTETS) { - ret[0] = (septets / MAX_USER_DATA_SEPTETS_WITH_HEADER) + 1; - ret[2] = MAX_USER_DATA_SEPTETS_WITH_HEADER - - (septets % MAX_USER_DATA_SEPTETS_WITH_HEADER); + ret[0] = (septets + (MAX_USER_DATA_SEPTETS_WITH_HEADER - 1)) / + MAX_USER_DATA_SEPTETS_WITH_HEADER; + ret[2] = (ret[0] * MAX_USER_DATA_SEPTETS_WITH_HEADER) - septets; } else { ret[0] = 1; ret[2] = MAX_USER_DATA_SEPTETS - septets; @@ -318,9 +318,9 @@ public class SmsMessage { ret[1] = messageBody.length(); if (octets > MAX_USER_DATA_BYTES) { // 6 is the size of the user data header - ret[0] = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1; - ret[2] = (MAX_USER_DATA_BYTES_WITH_HEADER - - (octets % MAX_USER_DATA_BYTES_WITH_HEADER))/2; + ret[0] = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) / + MAX_USER_DATA_BYTES_WITH_HEADER; + ret[2] = ((ret[0] * MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2; } else { ret[0] = 1; ret[2] = (MAX_USER_DATA_BYTES - octets)/2; diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 65d87f5..e95b2f9 100755 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -442,7 +442,7 @@ public class SmsMessage extends SmsMessageBase { */ public static TextEncodingDetails calculateLength(CharSequence messageBody, boolean use7bitOnly) { - return BearerData.calcTextEncodingDetails(messageBody.toString(), use7bitOnly); + return BearerData.calcTextEncodingDetails(messageBody, use7bitOnly); } /** diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index e9fea55..cf06dab 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -402,6 +402,7 @@ public final class BearerData { /** * Calculate the message text encoding length, fragmentation, and other details. * + * @param msg message text * @param force7BitEncoding ignore (but still count) illegal characters if true * @return septet count, or -1 on failure */ @@ -424,9 +425,10 @@ public final class BearerData { ted.codeUnitCount = msg.length(); int octets = ted.codeUnitCount * 2; if (octets > MAX_USER_DATA_BYTES) { - ted.msgCount = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1; - ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES_WITH_HEADER - - (octets % MAX_USER_DATA_BYTES_WITH_HEADER))/2; + ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) / + MAX_USER_DATA_BYTES_WITH_HEADER; + ted.codeUnitsRemaining = ((ted.msgCount * + MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2; } else { ted.msgCount = 1; ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2; @@ -801,7 +803,7 @@ public final class BearerData { * * @param bData an instance of BearerData. * - * @return data byte array of raw encoded SMS bearer data. + * @return byte array of raw encoded SMS bearer data. */ public static byte[] encode(BearerData bData) { bData.hasUserDataHeader = ((bData.userData != null) && diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 278e1ba..a77484a 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -795,9 +795,10 @@ public class SmsMessage extends SmsMessageBase { int septets = GsmAlphabet.countGsmSeptets(msgBody, !use7bitOnly); ted.codeUnitCount = septets; if (septets > MAX_USER_DATA_SEPTETS) { - ted.msgCount = (septets / MAX_USER_DATA_SEPTETS_WITH_HEADER) + 1; - ted.codeUnitsRemaining = MAX_USER_DATA_SEPTETS_WITH_HEADER - - (septets % MAX_USER_DATA_SEPTETS_WITH_HEADER); + ted.msgCount = (septets + (MAX_USER_DATA_SEPTETS_WITH_HEADER - 1)) / + MAX_USER_DATA_SEPTETS_WITH_HEADER; + ted.codeUnitsRemaining = (ted.msgCount * + MAX_USER_DATA_SEPTETS_WITH_HEADER) - septets; } else { ted.msgCount = 1; ted.codeUnitsRemaining = MAX_USER_DATA_SEPTETS - septets; @@ -807,9 +808,10 @@ public class SmsMessage extends SmsMessageBase { int octets = msgBody.length() * 2; ted.codeUnitCount = msgBody.length(); if (octets > MAX_USER_DATA_BYTES) { - ted.msgCount = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1; - ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES_WITH_HEADER - - (octets % MAX_USER_DATA_BYTES_WITH_HEADER))/2; + ted.msgCount = (octets + (MAX_USER_DATA_BYTES_WITH_HEADER - 1)) / + MAX_USER_DATA_BYTES_WITH_HEADER; + ted.codeUnitsRemaining = ((ted.msgCount * + MAX_USER_DATA_BYTES_WITH_HEADER) - octets) / 2; } else { ted.msgCount = 1; ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2; diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java new file mode 100644 index 0000000..b214887 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java @@ -0,0 +1,194 @@ +/* + * 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.internal.telephony; + +import android.telephony.SmsMessage; +import android.telephony.TelephonyManager; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; + +public class SmsMessageBodyTest extends AndroidTestCase { + + private static final String sAsciiChars = "@$_ !\"#%&'()*+,-./0123456789" + + ":;<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\n\r"; + private static final String sGsmBasicChars = "\u00a3\u00a5\u00e8\u00e9" + + "\u00f9\u00ec\u00f2\u00c7\u00d8\u00f8\u00c5\u00e5\u0394\u03a6" + + "\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u00c6\u00e6" + + "\u00df\u00c9\u00a4\u00a1\u00c4\u00d6\u00d1\u00dc\u00a7\u00bf" + + "\u00e4\u00f6\u00f1\u00fc\u00e0"; + private static final String sGsmExtendedAsciiChars = "{|}\\[~]^\f"; + private static final String sGsmExtendedEuroSymbol = "\u20ac"; + private static final String sUnicodeChars = "\u4e00\u4e01\u4e02\u4e03" + + "\u4e04\u4e05\u4e06\u4e07\u4e08\u4e09\u4e0a\u4e0b\u4e0c\u4e0d" + + "\u4e0e\u4e0f\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048" + + "\u30a1\u30a2\u30a3\u30a4\u30a5\u30a6\u30a7\u30a8" + + "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18" + + "\uff70\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78" + + "\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408" + + "\u00a2\u00a9\u00ae\u2122"; + + private static final int sTestLengthCount = 12; + + private static final int[] sSeptetTestLengths = + { 0, 1, 2, 80, 159, 160, 161, 240, 305, 306, 307, 320}; + + private static final int[] sUnicodeTestLengths = + { 0, 1, 2, 35, 69, 70, 71, 100, 133, 134, 135, 160}; + + private static final int[] sTestMsgCounts = + { 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3}; + + private static final int[] sSeptetUnitsRemaining = + {160, 159, 158, 80, 1, 0, 145, 66, 1, 0, 152, 139}; + + private static final int[] sUnicodeUnitsRemaining = + { 70, 69, 68, 35, 1, 0, 63, 34, 1, 0, 66, 41}; + + + @SmallTest + public void testCalcLengthAscii() throws Exception { + StringBuilder sb = new StringBuilder(320); + int[] values = {0, 0, 0, SmsMessage.ENCODING_7BIT}; + int startPos = 0; + int asciiCharsLen = sAsciiChars.length(); + + for (int i = 0; i < sTestLengthCount; i++) { + int len = sSeptetTestLengths[i]; + assertTrue(sb.length() <= len); + + while (sb.length() < len) { + int addCount = len - sb.length(); + int endPos = (asciiCharsLen - startPos > addCount) ? + (startPos + addCount) : asciiCharsLen; + sb.append(sAsciiChars, startPos, endPos); + startPos = (endPos == asciiCharsLen) ? 0 : endPos; + } + assertEquals(len, sb.length()); + + String testStr = sb.toString(); + values[0] = sTestMsgCounts[i]; + values[1] = len; + values[2] = sSeptetUnitsRemaining[i]; + + callGsmLengthMethods(testStr, false, values); + callGsmLengthMethods(testStr, true, values); + callCdmaLengthMethods(testStr, false, values); + callCdmaLengthMethods(testStr, true, values); + } + } + + @SmallTest + public void testCalcLength7bitGsm() throws Exception { + // TODO + } + + @SmallTest + public void testCalcLength7bitGsmExtended() throws Exception { + // TODO + } + + @SmallTest + public void testCalcLengthUnicode() throws Exception { + StringBuilder sb = new StringBuilder(160); + int[] values = {0, 0, 0, SmsMessage.ENCODING_16BIT}; + int[] values7bit = {1, 0, 0, SmsMessage.ENCODING_7BIT}; + int startPos = 0; + int unicodeCharsLen = sUnicodeChars.length(); + + // start with length 1: empty string uses ENCODING_7BIT + for (int i = 1; i < sTestLengthCount; i++) { + int len = sUnicodeTestLengths[i]; + assertTrue(sb.length() <= len); + + while (sb.length() < len) { + int addCount = len - sb.length(); + int endPos = (unicodeCharsLen - startPos > addCount) ? + (startPos + addCount) : unicodeCharsLen; + sb.append(sUnicodeChars, startPos, endPos); + startPos = (endPos == unicodeCharsLen) ? 0 : endPos; + } + assertEquals(len, sb.length()); + + String testStr = sb.toString(); + values[0] = sTestMsgCounts[i]; + values[1] = len; + values[2] = sUnicodeUnitsRemaining[i]; + values7bit[1] = len; + values7bit[2] = MAX_USER_DATA_SEPTETS - len; + + callGsmLengthMethods(testStr, false, values); + callCdmaLengthMethods(testStr, false, values); + callGsmLengthMethods(testStr, true, values7bit); + callCdmaLengthMethods(testStr, true, values7bit); + } + } + + private void callGsmLengthMethods(CharSequence msgBody, boolean use7bitOnly, + int[] expectedValues) + { + // deprecated GSM-specific method + int[] values = android.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], values[0]); + assertEquals("codeUnitCount", expectedValues[1], values[1]); + assertEquals("codeUnitsRemaining", expectedValues[2], values[2]); + assertEquals("codeUnitSize", expectedValues[3], values[3]); + + int activePhone = TelephonyManager.getDefault().getPhoneType(); + if (TelephonyManager.PHONE_TYPE_GSM == activePhone) { + values = android.telephony.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], values[0]); + assertEquals("codeUnitCount", expectedValues[1], values[1]); + assertEquals("codeUnitsRemaining", expectedValues[2], values[2]); + assertEquals("codeUnitSize", expectedValues[3], values[3]); + } + + SmsMessageBase.TextEncodingDetails ted = + com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], ted.msgCount); + assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount); + assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining); + assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize); + } + + private void callCdmaLengthMethods(CharSequence msgBody, boolean use7bitOnly, + int[] expectedValues) + { + int activePhone = TelephonyManager.getDefault().getPhoneType(); + if (TelephonyManager.PHONE_TYPE_CDMA == activePhone) { + int[] values = android.telephony.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], values[0]); + assertEquals("codeUnitCount", expectedValues[1], values[1]); + assertEquals("codeUnitsRemaining", expectedValues[2], values[2]); + assertEquals("codeUnitSize", expectedValues[3], values[3]); + } + + SmsMessageBase.TextEncodingDetails ted = + com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], ted.msgCount); + assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount); + assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining); + assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize); + + ted = com.android.internal.telephony.cdma.sms.BearerData.calcTextEncodingDetails(msgBody, use7bitOnly); + assertEquals("msgCount", expectedValues[0], ted.msgCount); + assertEquals("codeUnitCount", expectedValues[1], ted.codeUnitCount); + assertEquals("codeUnitsRemaining", expectedValues[2], ted.codeUnitsRemaining); + assertEquals("codeUnitSize", expectedValues[3], ted.codeUnitSize); + } +} diff --git a/tests/BatteryWaster/res/layout/battery_waster.xml b/tests/BatteryWaster/res/layout/battery_waster.xml index e1cb6bf..57a5b55 100644 --- a/tests/BatteryWaster/res/layout/battery_waster.xml +++ b/tests/BatteryWaster/res/layout/battery_waster.xml @@ -25,11 +25,23 @@ android:layout_height="wrap_content" android:layout_marginLeft="25dp" android:layout_marginTop="25dp" + android:saveEnabled="false" android:textSize="18sp" android:textColor="#ffffffff" android:text="@string/waste_away" /> + <CheckBox android:id="@+id/checkbox_wake" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="25dp" + android:layout_marginTop="25dp" + android:saveEnabled="false" + android:textSize="18sp" + android:textColor="#ffffffff" + android:text="@string/wake_away" + /> + <ScrollView android:id="@+id/scroll" android:layout_width="match_parent" android:layout_height="0px" diff --git a/tests/BatteryWaster/res/values/strings.xml b/tests/BatteryWaster/res/values/strings.xml index 46c5fa1..a3b849a 100644 --- a/tests/BatteryWaster/res/values/strings.xml +++ b/tests/BatteryWaster/res/values/strings.xml @@ -18,5 +18,7 @@ <string name="waste_away">Discharge my battery!</string> + <string name="wake_away">Keep my device awake!</string> + </resources> diff --git a/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java b/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java index 8ea7e00..48c4520 100644 --- a/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java +++ b/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java @@ -39,8 +39,11 @@ public class BatteryWaster extends Activity { DateFormat mDateFormat; IntentFilter mFilter; PowerManager.WakeLock mWakeLock; + PowerManager.WakeLock mPartialWakeLock; SpinThread mThread; + boolean mWasting, mWaking; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -50,6 +53,7 @@ public class BatteryWaster extends Activity { setContentView(R.layout.battery_waster); findViewById(R.id.checkbox).setOnClickListener(mClickListener); + findViewById(R.id.checkbox_wake).setOnClickListener(mWakeClickListener); mLog = (TextView)findViewById(R.id.log); mDateFormat = DateFormat.getInstance(); @@ -63,13 +67,27 @@ public class BatteryWaster extends Activity { PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "BatteryWaster"); mWakeLock.setReferenceCounted(false); + mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BatteryWaster"); + mPartialWakeLock.setReferenceCounted(false); } @Override public void onPause() { + super.onPause(); stopRunning(); } + @Override + public void onDestroy() { + super.onDestroy(); + if (mWakeLock.isHeld()) { + mWakeLock.release(); + } + if (mPartialWakeLock.isHeld()) { + mPartialWakeLock.release(); + } + } + View.OnClickListener mClickListener = new View.OnClickListener() { public void onClick(View v) { CheckBox checkbox = (CheckBox)v; @@ -81,23 +99,63 @@ public class BatteryWaster extends Activity { } }; + View.OnClickListener mWakeClickListener = new View.OnClickListener() { + public void onClick(View v) { + CheckBox checkbox = (CheckBox)v; + if (checkbox.isChecked()) { + mWaking = true; + updateWakeLock(); + } else { + mWaking = false; + updateWakeLock(); + } + } + }; + void startRunning() { - log("Start"); - registerReceiver(mReceiver, mFilter); - mWakeLock.acquire(); - if (mThread == null) { - mThread = new SpinThread(); - mThread.start(); + if (!mWasting) { + log("Start"); + registerReceiver(mReceiver, mFilter); + mWasting = true; + updateWakeLock(); + if (mThread == null) { + mThread = new SpinThread(); + mThread.start(); + } } } void stopRunning() { - log("Stop"); - unregisterReceiver(mReceiver); - mWakeLock.release(); - if (mThread != null) { - mThread.quit(); - mThread = null; + if (mWasting) { + log("Stop"); + unregisterReceiver(mReceiver); + mWasting = false; + updateWakeLock(); + if (mThread != null) { + mThread.quit(); + mThread = null; + } + } + } + + void updateWakeLock() { + if (mWasting) { + if (!mWakeLock.isHeld()) { + mWakeLock.acquire(); + } + } else { + if (mWakeLock.isHeld()) { + mWakeLock.release(); + } + } + if (mWaking) { + if (!mPartialWakeLock.isHeld()) { + mPartialWakeLock.acquire(); + } + } else { + if (mPartialWakeLock.isHeld()) { + mPartialWakeLock.release(); + } } } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 181b4c8..775dc24 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -189,5 +189,14 @@ </intent-filter> </activity> + <activity + android:name="StackActivity" + android:label="_Stacks"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> </manifest> diff --git a/tests/HwAccelerationTest/res/layout/list_activity.xml b/tests/HwAccelerationTest/res/layout/list_activity.xml index f548f53..6bba370 100644 --- a/tests/HwAccelerationTest/res/layout/list_activity.xml +++ b/tests/HwAccelerationTest/res/layout/list_activity.xml @@ -19,6 +19,31 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginRight="3dip" + + android:text="Add" /> + + <Button + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" + android:layout_marginLeft="3dip" + android:layout_marginRight="10dip" + + android:text="Remove" /> + + </LinearLayout> + <ListView android:id="@+id/list" android:layout_width="match_parent" diff --git a/tests/HwAccelerationTest/res/layout/stack.xml b/tests/HwAccelerationTest/res/layout/stack.xml new file mode 100644 index 0000000..b4d2d73a --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/stack.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:paddingTop="0dp" + android:paddingBottom="0dp" + android:paddingLeft="12dp" + android:paddingRight="12dp" + android:focusable="true"> + <StackView + android:id="@+id/stack_view" + android:layout_width="348px" + android:layout_height="374px" + android:layout_gravity="center" + android:background="#00000000" + android:cacheColorHint="#00000000" + android:autoStart="true" /> +</FrameLayout> diff --git a/tests/HwAccelerationTest/res/layout/stack_item.xml b/tests/HwAccelerationTest/res/layout/stack_item.xml new file mode 100644 index 0000000..3504018 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/stack_item.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/stack_item" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <FrameLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <ImageView android:id="@+id/textview_icon" + android:layout_height="250dip" + android:layout_width="250dip" + android:layout_gravity="center" /> + <TextView android:id="@+id/mini_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + </FrameLayout> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:layout_gravity="center" /> +</FrameLayout>
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java index fd7a1e6..2ba249a 100644 --- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java +++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/QuickRejectActivity.java @@ -23,6 +23,7 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Bundle; +import android.util.Log; import android.view.View; @SuppressWarnings({"UnusedDeclaration"}) @@ -51,6 +52,32 @@ public class QuickRejectActivity extends Activity { protected void onDraw(Canvas canvas) { super.onDraw(canvas); + int count = canvas.getSaveCount(); + Log.d("OpenGLRenderer", "count=" + count); + count = canvas.save(); + Log.d("OpenGLRenderer", "count after save=" + count); + count = canvas.getSaveCount(); + Log.d("OpenGLRenderer", "getSaveCount after save=" + count); + canvas.restore(); + count = canvas.getSaveCount(); + Log.d("OpenGLRenderer", "count after restore=" + count); + canvas.save(); + Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount()); + canvas.save(); + Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount()); + canvas.save(); + Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount()); + canvas.restoreToCount(count); + count = canvas.getSaveCount(); + Log.d("OpenGLRenderer", "count after restoreToCount=" + count); + count = canvas.saveLayer(0, 0, 10, 10, mBitmapPaint, Canvas.ALL_SAVE_FLAG); + Log.d("OpenGLRenderer", "count after saveLayer=" + count); + count = canvas.getSaveCount(); + Log.d("OpenGLRenderer", "getSaveCount after saveLayer=" + count); + canvas.restore(); + count = canvas.getSaveCount(); + Log.d("OpenGLRenderer", "count after restore=" + count); + canvas.save(); canvas.clipRect(0.0f, 0.0f, 40.0f, 40.0f); canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBitmapPaint); diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/StackActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/StackActivity.java new file mode 100644 index 0000000..5c8db6e --- /dev/null +++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/StackActivity.java @@ -0,0 +1,59 @@ +/* + * 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.google.android.test.hwui; + +import android.app.Activity; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.StackView; +import android.widget.TextView; + +@SuppressWarnings({"UnusedDeclaration"}) +public class StackActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.stack); + + StackView stack = (StackView) findViewById(R.id.stack_view); + stack.setAdapter(new ArrayAdapter<Drawable>(this, android.R.layout.simple_list_item_1, + android.R.id.text1, new Drawable[] { + getResources().getDrawable(R.drawable.sunset1), + getResources().getDrawable(R.drawable.sunset2), + }) { + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View item = convertView; + if (item == null) { + item = LayoutInflater.from(getContext()).inflate( + R.layout.stack_item, null, false); + } + ((ImageView) item.findViewById(R.id.textview_icon)).setImageDrawable( + getItem(position % getCount())); + ((TextView) item.findViewById(R.id.mini_text)).setText("" + position); + return item; + } + }); + stack.setDisplayedChild(0); + } +} diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 5855b56..31c1722 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1880,7 +1880,7 @@ addProguardKeepRule(ProguardKeepSet* keep, const String8& inClassName, className.append(inClassName); } } - + String8 rule("-keep class "); rule += className; rule += " { <init>(...); }"; @@ -1955,7 +1955,7 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass if (tag == "application") { inApplication = true; keepTag = true; - + String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android", "backupAgent", &error); if (agent.length() > 0) { @@ -1988,9 +1988,17 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass return NO_ERROR; } +struct NamespaceAttributePair { + const char* ns; + const char* attr; + + NamespaceAttributePair(const char* n, const char* a) : ns(n), attr(a) {} + NamespaceAttributePair() : ns(NULL), attr(NULL) {} +}; + status_t writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile, - const char* startTag, const char* altTag) + const char* startTag, const KeyedVector<String8, NamespaceAttributePair>* tagAttrPairs) { status_t err; ResXMLTree tree; @@ -2020,7 +2028,7 @@ writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile, return NO_ERROR; } } - + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code != ResXMLTree::START_TAG) { continue; @@ -2031,16 +2039,21 @@ writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile, if (strchr(tag.string(), '.')) { addProguardKeepRule(keep, tag, NULL, layoutFile->getPrintableSource(), tree.getLineNumber()); - } else if (altTag != NULL && tag == altTag) { - ssize_t classIndex = tree.indexOfAttribute(NULL, "class"); - if (classIndex < 0) { - fprintf(stderr, "%s:%d: <view> does not have class attribute.\n", - layoutFile->getPrintableSource().string(), tree.getLineNumber()); - } else { - size_t len; - addProguardKeepRule(keep, - String8(tree.getAttributeStringValue(classIndex, &len)), NULL, - layoutFile->getPrintableSource(), tree.getLineNumber()); + } else if (tagAttrPairs != NULL) { + ssize_t tagIndex = tagAttrPairs->indexOfKey(tag); + if (tagIndex >= 0) { + const NamespaceAttributePair& nsAttr = tagAttrPairs->valueAt(tagIndex); + ssize_t attrIndex = tree.indexOfAttribute(nsAttr.ns, nsAttr.attr); + if (attrIndex < 0) { + // fprintf(stderr, "%s:%d: <%s> does not have attribute %s:%s.\n", + // layoutFile->getPrintableSource().string(), tree.getLineNumber(), + // tag.string(), nsAttr.ns, nsAttr.attr); + } else { + size_t len; + addProguardKeepRule(keep, + String8(tree.getAttributeStringValue(attrIndex, &len)), NULL, + layoutFile->getPrintableSource(), tree.getLineNumber()); + } } } } @@ -2048,25 +2061,42 @@ writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile, return NO_ERROR; } +static void addTagAttrPair(KeyedVector<String8, NamespaceAttributePair>* dest, + const char* tag, const char* ns, const char* attr) { + dest->add(String8(tag), NamespaceAttributePair(ns, attr)); +} + status_t writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) { status_t err; + + // tag:attribute pairs that should be checked in layout files. + KeyedVector<String8, NamespaceAttributePair> kLayoutTagAttrPairs; + addTagAttrPair(&kLayoutTagAttrPairs, "view", NULL, "class"); + addTagAttrPair(&kLayoutTagAttrPairs, "fragment", RESOURCES_ANDROID_NAMESPACE, "name"); + + // tag:attribute pairs that should be checked in xml files. + KeyedVector<String8, NamespaceAttributePair> kXmlTagAttrPairs; + addTagAttrPair(&kXmlTagAttrPairs, "PreferenceScreen", RESOURCES_ANDROID_NAMESPACE, "fragment"); + addTagAttrPair(&kXmlTagAttrPairs, "Header", RESOURCES_ANDROID_NAMESPACE, "fragment"); + const Vector<sp<AaptDir> >& dirs = assets->resDirs(); const size_t K = dirs.size(); for (size_t k=0; k<K; k++) { const sp<AaptDir>& d = dirs.itemAt(k); const String8& dirName = d->getLeaf(); const char* startTag = NULL; - const char* altTag = NULL; + const KeyedVector<String8, NamespaceAttributePair>* tagAttrPairs = NULL; if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) { - altTag = "view"; + tagAttrPairs = &kLayoutTagAttrPairs; } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) { startTag = "PreferenceScreen"; + tagAttrPairs = &kXmlTagAttrPairs; } else { continue; } - + const KeyedVector<String8,sp<AaptGroup> > groups = d->getFiles(); const size_t N = groups.size(); for (size_t i=0; i<N; i++) { @@ -2074,7 +2104,7 @@ writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles(); const size_t M = files.size(); for (size_t j=0; j<M; j++) { - err = writeProguardForXml(keep, files.valueAt(j), startTag, altTag); + err = writeProguardForXml(keep, files.valueAt(j), startTag, tagAttrPairs); if (err < 0) { return err; } diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java index abdc9d7..f4be839 100644 --- a/voip/java/android/net/sip/SipAudioCall.java +++ b/voip/java/android/net/sip/SipAudioCall.java @@ -191,11 +191,8 @@ public interface SipAudioCall { */ void continueCall() throws SipException; - /** Puts the device to in-call mode. */ - void setInCallMode(); - /** Puts the device to speaker mode. */ - void setSpeakerMode(); + void setSpeakerMode(boolean speakerMode); /** Toggles mute. */ void toggleMute(); diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java index 474bc4b..7161309 100644 --- a/voip/java/android/net/sip/SipAudioCallImpl.java +++ b/voip/java/android/net/sip/SipAudioCallImpl.java @@ -485,16 +485,9 @@ public class SipAudioCallImpl extends SipSessionAdapter return mMuted; } - public synchronized void setInCallMode() { + public synchronized void setSpeakerMode(boolean speakerMode) { ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setSpeakerphoneOn(false); - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setMode(AudioManager.MODE_NORMAL); - } - - public synchronized void setSpeakerMode() { - ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)) - .setSpeakerphoneOn(true); + .setSpeakerphoneOn(speakerMode); } public void sendDtmf(int code) { @@ -587,8 +580,15 @@ public class SipAudioCallImpl extends SipSessionAdapter Log.d(TAG, " not sending"); audioStream.setMode(RtpStream.MODE_RECEIVE_ONLY); } + } else { + /* The recorder volume will be very low if the device is in + * IN_CALL mode. Therefore, we have to set the mode to NORMAL + * in order to have the normal microphone level. + */ + ((AudioManager) mContext.getSystemService + (Context.AUDIO_SERVICE)) + .setMode(AudioManager.MODE_NORMAL); } - setInCallMode(); AudioGroup audioGroup = new AudioGroup(); audioStream.join(audioGroup); @@ -614,7 +614,6 @@ public class SipAudioCallImpl extends SipSessionAdapter mRtpSession = null; } } - setInCallMode(); } private int getLocalMediaPort() { |
