diff options
37 files changed, 792 insertions, 421 deletions
diff --git a/api/current.xml b/api/current.xml index 58b9c17..b256d3f 100644 --- a/api/current.xml +++ b/api/current.xml @@ -25428,6 +25428,17 @@ visibility="public" > </field> +<field name="ENABLE_CAR_MODE_GO_CAR_HOME" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="MODE_NIGHT_AUTO" type="int" transient="false" @@ -89029,7 +89040,7 @@ <method name="getMobileRxBytes" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89040,7 +89051,7 @@ <method name="getMobileRxPackets" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89051,7 +89062,7 @@ <method name="getMobileTxBytes" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89062,7 +89073,7 @@ <method name="getMobileTxPackets" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89073,7 +89084,7 @@ <method name="getTotalRxBytes" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89084,7 +89095,7 @@ <method name="getTotalRxPackets" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89095,7 +89106,7 @@ <method name="getTotalTxBytes" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89106,7 +89117,7 @@ <method name="getTotalTxPackets" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89117,7 +89128,7 @@ <method name="getUidRxBytes" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -89130,7 +89141,7 @@ <method name="getUidTxBytes" return="long" abstract="false" - native="false" + native="true" synchronized="false" static="true" final="false" @@ -90859,35 +90870,8 @@ <parameter name="userAgent" type="java.lang.String"> </parameter> </method> -<field name="DEFAULT_SYNC_MIN_GZIP_BYTES" - type="long" - transient="false" - volatile="false" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -</field> -</class> -<class name="HttpDateTime" - extends="java.lang.Object" - abstract="false" - static="false" - final="true" - deprecated="not deprecated" - visibility="public" -> -<constructor name="HttpDateTime" - type="android.net.http.HttpDateTime" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</constructor> -<method name="parse" - return="java.lang.Long" +<method name="parseDate" + return="long" abstract="false" native="false" synchronized="false" @@ -90896,11 +90880,19 @@ deprecated="not deprecated" visibility="public" > -<parameter name="timeString" type="java.lang.String"> +<parameter name="dateString" type="java.lang.String"> </parameter> -<exception name="IllegalArgumentException" type="java.lang.IllegalArgumentException"> -</exception> </method> +<field name="DEFAULT_SYNC_MIN_GZIP_BYTES" + type="long" + transient="false" + volatile="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="SslCertificate" extends="java.lang.Object" diff --git a/common/java/com/android/common/OperationScheduler.java b/common/java/com/android/common/OperationScheduler.java index 0a48fe7..1786957 100644 --- a/common/java/com/android/common/OperationScheduler.java +++ b/common/java/com/android/common/OperationScheduler.java @@ -17,7 +17,7 @@ package com.android.common; import android.content.SharedPreferences; -import android.net.http.HttpDateTime; +import android.net.http.AndroidHttpClient; import android.text.format.Time; import java.util.Map; @@ -124,7 +124,8 @@ public class OperationScheduler { } /** - * Compute the time of the next operation. Does not modify any state. + * Compute the time of the next operation. Does not modify any state + * (unless the clock rolls backwards, in which case timers are reset). * * @param options to use for this computation. * @return the wall clock time ({@link System#currentTimeMillis()}) when the @@ -143,11 +144,11 @@ public class OperationScheduler { // clipped to the current time so we don't languish forever. int errorCount = mStorage.getInt(PREFIX + "errorCount", 0); - long now = System.currentTimeMillis(); + long now = currentTimeMillis(); long lastSuccessTimeMillis = getTimeBefore(PREFIX + "lastSuccessTimeMillis", now); long lastErrorTimeMillis = getTimeBefore(PREFIX + "lastErrorTimeMillis", now); long triggerTimeMillis = mStorage.getLong(PREFIX + "triggerTimeMillis", Long.MAX_VALUE); - long moratoriumSetMillis = mStorage.getLong(PREFIX + "moratoriumSetTimeMillis", 0); + long moratoriumSetMillis = getTimeBefore(PREFIX + "moratoriumSetTimeMillis", now); long moratoriumTimeMillis = getTimeBefore(PREFIX + "moratoriumTimeMillis", moratoriumSetMillis + options.maxMoratoriumMillis); @@ -155,9 +156,8 @@ public class OperationScheduler { if (options.periodicIntervalMillis > 0) { time = Math.min(time, lastSuccessTimeMillis + options.periodicIntervalMillis); } - if (time >= moratoriumTimeMillis - options.maxMoratoriumMillis) { - time = Math.max(time, moratoriumTimeMillis); - } + + time = Math.max(time, moratoriumTimeMillis); time = Math.max(time, lastSuccessTimeMillis + options.minTriggerMillis); if (errorCount > 0) { time = Math.max(time, lastErrorTimeMillis + options.backoffFixedMillis + @@ -205,7 +205,7 @@ public class OperationScheduler { /** * Request an operation to be performed at a certain time. The actual * scheduled time may be affected by error backoff logic and defined - * minimum intervals. + * minimum intervals. Use {@link Long#MAX_VALUE} to disable triggering. * * @param millis wall clock time ({@link System#currentTimeMillis()}) to * trigger another operation; 0 to trigger immediately @@ -218,13 +218,13 @@ public class OperationScheduler { * Forbid any operations until after a certain (absolute) time. * Limited by {@link #Options.maxMoratoriumMillis}. * - * @param millis wall clock time ({@link System#currentTimeMillis()}) to - * wait before attempting any more operations; 0 to remove moratorium + * @param millis wall clock time ({@link System#currentTimeMillis()}) + * when operations should be allowed again; 0 to remove moratorium */ public void setMoratoriumTimeMillis(long millis) { mStorage.edit() .putLong(PREFIX + "moratoriumTimeMillis", millis) - .putLong(PREFIX + "moratoriumSetTimeMillis", System.currentTimeMillis()) + .putLong(PREFIX + "moratoriumSetTimeMillis", currentTimeMillis()) .commit(); } @@ -239,11 +239,11 @@ public class OperationScheduler { public boolean setMoratoriumTimeHttp(String retryAfter) { try { long ms = Long.valueOf(retryAfter) * 1000; - setMoratoriumTimeMillis(ms + System.currentTimeMillis()); + setMoratoriumTimeMillis(ms + currentTimeMillis()); return true; } catch (NumberFormatException nfe) { try { - setMoratoriumTimeMillis(HttpDateTime.parse(retryAfter)); + setMoratoriumTimeMillis(AndroidHttpClient.parseDate(retryAfter)); return true; } catch (IllegalArgumentException iae) { return false; @@ -269,13 +269,12 @@ public class OperationScheduler { public void onSuccess() { resetTransientError(); resetPermanentError(); - long now = System.currentTimeMillis(); mStorage.edit() .remove(PREFIX + "errorCount") .remove(PREFIX + "lastErrorTimeMillis") .remove(PREFIX + "permanentError") .remove(PREFIX + "triggerTimeMillis") - .putLong(PREFIX + "lastSuccessTimeMillis", now).commit(); + .putLong(PREFIX + "lastSuccessTimeMillis", currentTimeMillis()).commit(); } /** @@ -284,8 +283,7 @@ public class OperationScheduler { * purposes. */ public void onTransientError() { - long now = System.currentTimeMillis(); - mStorage.edit().putLong(PREFIX + "lastErrorTimeMillis", now).commit(); + mStorage.edit().putLong(PREFIX + "lastErrorTimeMillis", currentTimeMillis()).commit(); mStorage.edit().putInt(PREFIX + "errorCount", mStorage.getInt(PREFIX + "errorCount", 0) + 1).commit(); } @@ -338,4 +336,13 @@ public class OperationScheduler { } return out.append("]").toString(); } + + /** + * Gets the current time. Can be overridden for unit testing. + * + * @return {@link System#currentTimeMillis()} + */ + protected long currentTimeMillis() { + return System.currentTimeMillis(); + } } diff --git a/common/tests/src/com/android/common/OperationSchedulerTest.java b/common/tests/src/com/android/common/OperationSchedulerTest.java index 866d1a8..955508f 100644 --- a/common/tests/src/com/android/common/OperationSchedulerTest.java +++ b/common/tests/src/com/android/common/OperationSchedulerTest.java @@ -22,19 +22,34 @@ import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; public class OperationSchedulerTest extends AndroidTestCase { + /** + * OperationScheduler subclass which uses an artificial time. + * Set {@link #timeMillis} to whatever value you like. + */ + private class TimeTravelScheduler extends OperationScheduler { + static final long DEFAULT_TIME = 1250146800000L; // 13-Aug-2009, 12:00:00 am + public long timeMillis = DEFAULT_TIME; + + @Override + protected long currentTimeMillis() { return timeMillis; } + public TimeTravelScheduler() { super(getFreshStorage()); } + } + + private SharedPreferences getFreshStorage() { + SharedPreferences sp = getContext().getSharedPreferences("OperationSchedulerTest", 0); + sp.edit().clear().commit(); + return sp; + } + @MediumTest public void testScheduler() throws Exception { - String name = "OperationSchedulerTest.testScheduler"; - SharedPreferences storage = getContext().getSharedPreferences(name, 0); - storage.edit().clear().commit(); - - OperationScheduler scheduler = new OperationScheduler(storage); + TimeTravelScheduler scheduler = new TimeTravelScheduler(); OperationScheduler.Options options = new OperationScheduler.Options(); assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); assertEquals(0, scheduler.getLastSuccessTimeMillis()); assertEquals(0, scheduler.getLastAttemptTimeMillis()); - long beforeTrigger = System.currentTimeMillis(); + long beforeTrigger = scheduler.timeMillis; scheduler.setTriggerTimeMillis(beforeTrigger + 1000000); assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); @@ -51,33 +66,26 @@ public class OperationSchedulerTest extends AndroidTestCase { assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); // Backoff interval after an error - long beforeError = System.currentTimeMillis(); + long beforeError = (scheduler.timeMillis += 100); scheduler.onTransientError(); - long afterError = System.currentTimeMillis(); assertEquals(0, scheduler.getLastSuccessTimeMillis()); - assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis()); - assertTrue(afterError >= scheduler.getLastAttemptTimeMillis()); + assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); options.backoffFixedMillis = 1000000; options.backoffIncrementalMillis = 500000; - assertTrue(beforeError + 1500000 <= scheduler.getNextTimeMillis(options)); - assertTrue(afterError + 1500000 >= scheduler.getNextTimeMillis(options)); + assertEquals(beforeError + 1500000, scheduler.getNextTimeMillis(options)); // Two errors: backoff interval increases - beforeError = System.currentTimeMillis(); + beforeError = (scheduler.timeMillis += 100); scheduler.onTransientError(); - afterError = System.currentTimeMillis(); - assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis()); - assertTrue(afterError >= scheduler.getLastAttemptTimeMillis()); - assertTrue(beforeError + 2000000 <= scheduler.getNextTimeMillis(options)); - assertTrue(afterError + 2000000 >= scheduler.getNextTimeMillis(options)); + assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); + assertEquals(beforeError + 2000000, scheduler.getNextTimeMillis(options)); // Reset transient error: no backoff interval scheduler.resetTransientError(); assertEquals(0, scheduler.getLastSuccessTimeMillis()); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); - assertTrue(beforeError <= scheduler.getLastAttemptTimeMillis()); - assertTrue(afterError >= scheduler.getLastAttemptTimeMillis()); + assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); // Permanent error holds true even if transient errors are reset // However, we remember that the transient error was reset... @@ -89,30 +97,26 @@ public class OperationSchedulerTest extends AndroidTestCase { assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); // Success resets the trigger - long beforeSuccess = System.currentTimeMillis(); + long beforeSuccess = (scheduler.timeMillis += 100); scheduler.onSuccess(); - long afterSuccess = System.currentTimeMillis(); - assertTrue(beforeSuccess <= scheduler.getLastAttemptTimeMillis()); - assertTrue(afterSuccess >= scheduler.getLastAttemptTimeMillis()); - assertTrue(beforeSuccess <= scheduler.getLastSuccessTimeMillis()); - assertTrue(afterSuccess >= scheduler.getLastSuccessTimeMillis()); + assertEquals(beforeSuccess, scheduler.getLastAttemptTimeMillis()); + assertEquals(beforeSuccess, scheduler.getLastSuccessTimeMillis()); assertEquals(Long.MAX_VALUE, scheduler.getNextTimeMillis(options)); // The moratorium is not reset by success! - scheduler.setTriggerTimeMillis(beforeSuccess + 500000); + scheduler.setTriggerTimeMillis(0); assertEquals(beforeTrigger + 1500000, scheduler.getNextTimeMillis(options)); scheduler.setMoratoriumTimeMillis(0); - assertEquals(beforeSuccess + 500000, scheduler.getNextTimeMillis(options)); + assertEquals(beforeSuccess, scheduler.getNextTimeMillis(options)); // Periodic interval after success options.periodicIntervalMillis = 250000; - assertTrue(beforeSuccess + 250000 <= scheduler.getNextTimeMillis(options)); - assertTrue(afterSuccess + 250000 >= scheduler.getNextTimeMillis(options)); + scheduler.setTriggerTimeMillis(Long.MAX_VALUE); + assertEquals(beforeSuccess + 250000, scheduler.getNextTimeMillis(options)); // Trigger minimum is also since the last success options.minTriggerMillis = 1000000; - assertTrue(beforeSuccess + 1000000 <= scheduler.getNextTimeMillis(options)); - assertTrue(afterSuccess + 1000000 >= scheduler.getNextTimeMillis(options)); + assertEquals(beforeSuccess + 1000000, scheduler.getNextTimeMillis(options)); } @SmallTest @@ -138,23 +142,19 @@ public class OperationSchedulerTest extends AndroidTestCase { @SmallTest public void testMoratoriumWithHttpDate() throws Exception { - String name = "OperationSchedulerTest.testMoratoriumWithHttpDate"; - SharedPreferences storage = getContext().getSharedPreferences(name, 0); - storage.edit().clear().commit(); - - OperationScheduler scheduler = new OperationScheduler(storage); + TimeTravelScheduler scheduler = new TimeTravelScheduler(); OperationScheduler.Options options = new OperationScheduler.Options(); - long beforeTrigger = System.currentTimeMillis(); + long beforeTrigger = scheduler.timeMillis; scheduler.setTriggerTimeMillis(beforeTrigger + 1000000); assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); scheduler.setMoratoriumTimeMillis(beforeTrigger + 2000000); assertEquals(beforeTrigger + 2000000, scheduler.getNextTimeMillis(options)); - long beforeMoratorium = System.currentTimeMillis(); + long beforeMoratorium = scheduler.timeMillis; assertTrue(scheduler.setMoratoriumTimeHttp("3000")); - long afterMoratorium = System.currentTimeMillis(); + long afterMoratorium = scheduler.timeMillis; assertTrue(beforeMoratorium + 3000000 <= scheduler.getNextTimeMillis(options)); assertTrue(afterMoratorium + 3000000 >= scheduler.getNextTimeMillis(options)); @@ -164,4 +164,56 @@ public class OperationSchedulerTest extends AndroidTestCase { assertFalse(scheduler.setMoratoriumTimeHttp("not actually a date")); } + + @SmallTest + public void testClockRollbackScenario() throws Exception { + TimeTravelScheduler scheduler = new TimeTravelScheduler(); + OperationScheduler.Options options = new OperationScheduler.Options(); + options.minTriggerMillis = 2000; + + // First, set up a scheduler with reasons to wait: a transient + // error with backoff and a moratorium for a few minutes. + + long beforeTrigger = scheduler.timeMillis; + long triggerTime = beforeTrigger - 10000000; + scheduler.setTriggerTimeMillis(triggerTime); + assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); + assertEquals(0, scheduler.getLastAttemptTimeMillis()); + + long beforeSuccess = (scheduler.timeMillis += 100); + scheduler.onSuccess(); + scheduler.setTriggerTimeMillis(triggerTime); + assertEquals(beforeSuccess, scheduler.getLastAttemptTimeMillis()); + assertEquals(beforeSuccess + 2000, scheduler.getNextTimeMillis(options)); + + long beforeError = (scheduler.timeMillis += 100); + scheduler.onTransientError(); + assertEquals(beforeError, scheduler.getLastAttemptTimeMillis()); + assertEquals(beforeError + 5000, scheduler.getNextTimeMillis(options)); + + long beforeMoratorium = (scheduler.timeMillis += 100); + scheduler.setMoratoriumTimeMillis(beforeTrigger + 1000000); + assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); + + // Now set the time back a few seconds. + // The moratorium time should still be honored. + long beforeRollback = (scheduler.timeMillis = beforeTrigger - 10000); + assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options)); + + // The rollback also moved the last-attempt clock back to the rollback time. + assertEquals(scheduler.timeMillis, scheduler.getLastAttemptTimeMillis()); + + // But if we set the time back more than a day, the moratorium + // resets to the maximum moratorium (a day, by default), exposing + // the original trigger time. + beforeRollback = (scheduler.timeMillis = beforeTrigger - 100000000); + assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); + assertEquals(beforeRollback, scheduler.getLastAttemptTimeMillis()); + + // If we roll forward until after the re-set moratorium, then it expires. + scheduler.timeMillis = triggerTime + 5000000; + assertEquals(triggerTime, scheduler.getNextTimeMillis(options)); + assertEquals(beforeRollback, scheduler.getLastAttemptTimeMillis()); + assertEquals(beforeRollback, scheduler.getLastSuccessTimeMillis()); + } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 950f34f..9019af6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -67,6 +67,8 @@ import android.location.LocationManager; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; +import android.net.ThrottleManager; +import android.net.IThrottleManager; import android.net.Uri; import android.net.wifi.IWifiManager; import android.net.wifi.WifiManager; @@ -164,6 +166,7 @@ class ContextImpl extends Context { private static AlarmManager sAlarmManager; private static PowerManager sPowerManager; private static ConnectivityManager sConnectivityManager; + private static ThrottleManager sThrottleManager; private static WifiManager sWifiManager; private static LocationManager sLocationManager; private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs = @@ -929,6 +932,8 @@ class ContextImpl extends Context { return getPowerManager(); } else if (CONNECTIVITY_SERVICE.equals(name)) { return getConnectivityManager(); + } else if (THROTTLE_SERVICE.equals(name)) { + return getThrottleManager(); } else if (WIFI_SERVICE.equals(name)) { return getWifiManager(); } else if (NOTIFICATION_SERVICE.equals(name)) { @@ -1028,6 +1033,18 @@ class ContextImpl extends Context { return sConnectivityManager; } + private ThrottleManager getThrottleManager() + { + synchronized (sSync) { + if (sThrottleManager == null) { + IBinder b = ServiceManager.getService(THROTTLE_SERVICE); + IThrottleManager service = IThrottleManager.Stub.asInterface(b); + sThrottleManager = new ThrottleManager(service); + } + } + return sThrottleManager; + } + private WifiManager getWifiManager() { synchronized (sSync) { diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl index 7b668d2..7e9873e 100644 --- a/core/java/android/app/IUiModeManager.aidl +++ b/core/java/android/app/IUiModeManager.aidl @@ -25,7 +25,7 @@ interface IUiModeManager { * Enables the car mode. Only the system can do this. * @hide */ - void enableCarMode(); + void enableCarMode(int flags); /** * Disables the car mode. diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index a76dfb1..95451d6 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -116,13 +116,13 @@ public class UiModeManager { } /** - * Flag for use with {@link #disableCarMode(int)}: go to the normal - * home activity as part of the disable. Disabling this way ensures - * a clean transition between the current activity (in car mode) and - * the original home activity (which was typically last running without - * being in car mode). + * Flag for use with {@link #enableCarMode(int)}: go to the car + * home activity as part of the enable. Enabling this way ensures + * a clean transition between the current activity (in non-car-mode) and + * the car home activity that will serve as home while in car mode. This + * will switch to the car home activity even if we are already in car mode. */ - public static final int DISABLE_CAR_MODE_GO_HOME = 0x0001; + public static final int ENABLE_CAR_MODE_GO_CAR_HOME = 0x0001; /** * Force device into car mode, like it had been placed in the car dock. @@ -133,7 +133,7 @@ public class UiModeManager { public void enableCarMode(int flags) { if (mService != null) { try { - mService.enableCarMode(); + mService.enableCarMode(flags); } catch (RemoteException e) { Log.e(TAG, "disableCarMode: RemoteException", e); } @@ -141,6 +141,15 @@ public class UiModeManager { } /** + * Flag for use with {@link #disableCarMode(int)}: go to the normal + * home activity as part of the disable. Disabling this way ensures + * a clean transition between the current activity (in car mode) and + * the original home activity (which was typically last running without + * being in car mode). + */ + public static final int DISABLE_CAR_MODE_GO_HOME = 0x0001; + + /** * Turn off special mode if currently in car mode. * @param flags May be 0 or {@link #DISABLE_CAR_MODE_GO_HOME}. */ diff --git a/core/java/android/net/IThrottleManager.aidl b/core/java/android/net/IThrottleManager.aidl index 298de6e..a12469d 100644 --- a/core/java/android/net/IThrottleManager.aidl +++ b/core/java/android/net/IThrottleManager.aidl @@ -35,4 +35,6 @@ interface IThrottleManager long getCliffThreshold(String iface, int cliff); int getCliffLevel(String iface, int cliff); + + String getHelpUri(); } diff --git a/core/java/android/net/ThrottleManager.java b/core/java/android/net/ThrottleManager.java index 0500f6f..5fdac58 100644 --- a/core/java/android/net/ThrottleManager.java +++ b/core/java/android/net/ThrottleManager.java @@ -73,6 +73,12 @@ public class ThrottleManager */ public static final String EXTRA_THROTTLE_LEVEL = "level"; + /** + * Broadcast on boot and whenever the settings change. + * {@hide} + */ + public static final String POLICY_CHANGED_ACTION = "android.net.thrott.POLICY_CHANGED_ACTION"; + // {@hide} public static final int DIRECTION_TX = 0; // {@hide} @@ -103,6 +109,8 @@ public class ThrottleManager // @hide public static final int PERIOD_SECOND = 11; + + /** * returns a long of the ms from the epoch to the time the current cycle ends for the * named interface @@ -147,7 +155,7 @@ public class ThrottleManager /** * returns the number of bytes read+written after which a particular cliff - * takes effect on the named iface. Currently only cliff #0 is supported (1 step) + * takes effect on the named iface. Currently only cliff #1 is supported (1 step) * {@hide} */ public long getCliffThreshold(String iface, int cliff) { @@ -160,7 +168,7 @@ public class ThrottleManager /** * returns the thottling bandwidth (bps) for a given cliff # on the named iface. - * only cliff #0 is currently supported. + * only cliff #1 is currently supported. * {@hide} */ public int getCliffLevel(String iface, int cliff) { @@ -171,6 +179,19 @@ public class ThrottleManager } } + /** + * returns the help URI for throttling + * {@hide} + */ + public String getHelpUri() { + try { + return mService.getHelpUri(); + } catch (RemoteException e) { + return null; + } + } + + private IThrottleManager mService; /** diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java index 3517737..e07ee59 100644 --- a/core/java/android/net/http/AndroidHttpClient.java +++ b/core/java/android/net/http/AndroidHttpClient.java @@ -16,6 +16,7 @@ package android.net.http; +import com.android.internal.http.HttpDateTime; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; @@ -444,4 +445,22 @@ public final class AndroidHttpClient implements HttpClient { return builder.toString(); } + + /** + * Returns the date of the given HTTP date string. This method can identify + * and parse the date formats emitted by common HTTP servers, such as + * <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a>, + * <a href="http://www.ietf.org/rfc/rfc0850.txt">RFC 850</a>, + * <a href="http://www.ietf.org/rfc/rfc1036.txt">RFC 1036</a>, + * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a> and + * <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html">ANSI + * C's asctime()</a>. + * + * @return the number of milliseconds since Jan. 1, 1970, midnight GMT. + * @throws IllegalArgumentException if {@code dateString} is not a date or + * of an unsupported format. + */ + public static long parseDate(String dateString) { + return HttpDateTime.parse(dateString); + } } diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index b3ec114..b6dc1b5 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -304,9 +304,7 @@ public class RecoverySystem { /** * Reboots the device in order to install the given update * package. - * Requires the {@link android.Manifest.permission#REBOOT} - * and {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM} - * permissions. + * Requires the {@link android.Manifest.permission#REBOOT} permission. * * @param context the Context to use * @param packageFile the update package to install. Currently @@ -337,9 +335,7 @@ public class RecoverySystem { * sometimes called a "factory reset", which is something of a * misnomer because the system partition is not restored to its * factory state. - * Requires the {@link android.Manifest.permission#REBOOT} - * and {@link android.Manifest.permission#ACCESS_CACHE_FILESYSTEM} - * permissions. + * Requires the {@link android.Manifest.permission#REBOOT} permission. * * @param context the Context to use * diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index eb14815..e640005 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3333,6 +3333,13 @@ public final class Settings { public static final String THROTTLE_IFACE = "throttle_iface"; /** + * Help URI for data throttling policy + * @hide + */ + public static final String THROTTLE_HELP_URI = "throttle_help_uri"; + + + /** * @hide */ public static final String[] SETTINGS_TO_BACKUP = { diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index e56a6fe..bf94707 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -766,7 +766,7 @@ public final class ViewRoot extends Handler implements ViewParent, // make sure touch mode code executes by setting cached value // to opposite of the added touch mode. mAttachInfo.mInTouchMode = !mAddedTouchMode; - ensureTouchModeLocally(mAddedTouchMode, false); + ensureTouchModeLocally(mAddedTouchMode); } else { if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) { mAttachInfo.mContentInsets.set(mPendingContentInsets); @@ -983,7 +983,7 @@ public final class ViewRoot extends Handler implements ViewParent, } boolean focusChangedDueToTouchMode = ensureTouchModeLocally( - (relayoutResult&WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE) != 0, true); + (relayoutResult&WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight || contentInsetsChanged) { childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); @@ -1043,13 +1043,6 @@ public final class ViewRoot extends Handler implements ViewParent, startTime = SystemClock.elapsedRealtime(); } host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight); - if (mFirst) { - if (mAddedTouchMode) { - enterTouchMode(); - } else { - leaveTouchMode(); - } - } if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { if (!host.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_LAYOUT)) { @@ -1899,7 +1892,7 @@ public final class ViewRoot extends Handler implements ViewParent, mAttachInfo.mHasWindowFocus = hasWindowFocus; if (hasWindowFocus) { boolean inTouchMode = msg.arg2 != 0; - ensureTouchModeLocally(inTouchMode, true); + ensureTouchModeLocally(inTouchMode); if (mGlWanted) { checkEglErrors(); @@ -2009,17 +2002,16 @@ public final class ViewRoot extends Handler implements ViewParent, } // handle the change - return ensureTouchModeLocally(inTouchMode, true); + return ensureTouchModeLocally(inTouchMode); } /** * Ensure that the touch mode for this window is set, and if it is changing, * take the appropriate action. * @param inTouchMode Whether we want to be in touch mode. - * @param dispatchFocus * @return True if the touch mode changed and focus changed was changed as a result */ - private boolean ensureTouchModeLocally(boolean inTouchMode, boolean dispatchFocus) { + private boolean ensureTouchModeLocally(boolean inTouchMode) { if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current " + "touch mode is " + mAttachInfo.mInTouchMode); @@ -2028,7 +2020,7 @@ public final class ViewRoot extends Handler implements ViewParent, mAttachInfo.mInTouchMode = inTouchMode; mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode); - return dispatchFocus && (inTouchMode) ? enterTouchMode() : leaveTouchMode(); + return (inTouchMode) ? enterTouchMode() : leaveTouchMode(); } private boolean enterTouchMode() { diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index d19805e..d5058b0 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -17,8 +17,8 @@ package android.webkit; import android.content.Context; +import android.net.http.AndroidHttpClient; import android.net.http.Headers; -import android.net.http.HttpDateTime; import android.os.FileUtils; import android.util.Log; import java.io.File; @@ -716,7 +716,7 @@ public final class CacheManager { ret.expiresString = headers.getExpires(); if (ret.expiresString != null) { try { - ret.expires = HttpDateTime.parse(ret.expiresString); + ret.expires = AndroidHttpClient.parseDate(ret.expiresString); } catch (IllegalArgumentException ex) { // Take care of the special "-1" and "0" cases if ("-1".equals(ret.expiresString) @@ -831,7 +831,7 @@ public final class CacheManager { // 24 * 60 * 60 * 1000 long lastmod = System.currentTimeMillis() + 86400000; try { - lastmod = HttpDateTime.parse(ret.lastModified); + lastmod = AndroidHttpClient.parseDate(ret.lastModified); } catch (IllegalArgumentException ex) { Log.e(LOGTAG, "illegal lastModified: " + ret.lastModified); } diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 758a152..ac20791 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -18,7 +18,7 @@ package android.webkit; import android.net.ParseException; import android.net.WebAddress; -import android.net.http.HttpDateTime; +import android.net.http.AndroidHttpClient; import android.util.Log; @@ -939,7 +939,7 @@ public final class CookieManager { } if (name.equals(EXPIRES)) { try { - cookie.expires = HttpDateTime.parse(value); + cookie.expires = AndroidHttpClient.parseDate(value); } catch (IllegalArgumentException ex) { Log.e(LOGTAG, "illegal format for expires: " + value); diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index 1130e95..12b8c74 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -380,7 +380,8 @@ class LoadListener extends Handler implements EventHandler { } // At this point, mMimeType has been set to non-null. if (mIsMainPageLoader && mIsMainResourceLoader && mUserGesture && - Pattern.matches(XML_MIME_TYPE, mMimeType)) { + Pattern.matches(XML_MIME_TYPE, mMimeType) && + !mMimeType.equalsIgnoreCase("application/xhtml+xml")) { Intent i = new Intent(Intent.ACTION_VIEW); i.setDataAndType(Uri.parse(url()), mMimeType); ResolveInfo info = mContext.getPackageManager().resolveActivity(i, diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 4233af1..bbe52a3 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -475,6 +475,7 @@ public class WebView extends AbsoluteLayout private static final int MOTIONLESS_FALSE = 0; private static final int MOTIONLESS_PENDING = 1; private static final int MOTIONLESS_TRUE = 2; + private static final int MOTIONLESS_IGNORE = 3; private int mHeldMotionless; // whether support multi-touch @@ -1274,30 +1275,57 @@ public class WebView extends AbsoluteLayout * overwritten with this WebView's picture data. * @return True if the picture was successfully saved. */ - public boolean savePicture(Bundle b, File dest) { + public boolean savePicture(Bundle b, final File dest) { if (dest == null || b == null) { return false; } final Picture p = capturePicture(); - try { - final FileOutputStream out = new FileOutputStream(dest); - p.writeToStream(out); - out.close(); - // now update the bundle - b.putInt("scrollX", mScrollX); - b.putInt("scrollY", mScrollY); - b.putFloat("scale", mActualScale); - b.putFloat("textwrapScale", mTextWrapScale); - b.putBoolean("overview", mInZoomOverview); - return true; - } catch (FileNotFoundException e){ - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } - return false; + // Use a temporary file while writing to ensure the destination file + // contains valid data. + final File temp = new File(dest.getPath() + ".writing"); + new Thread(new Runnable() { + public void run() { + try { + FileOutputStream out = new FileOutputStream(temp); + p.writeToStream(out); + out.close(); + // Writing the picture succeeded, rename the temporary file + // to the destination. + temp.renameTo(dest); + } catch (Exception e) { + // too late to do anything about it. + } finally { + temp.delete(); + } + } + }).start(); + // now update the bundle + b.putInt("scrollX", mScrollX); + b.putInt("scrollY", mScrollY); + b.putFloat("scale", mActualScale); + b.putFloat("textwrapScale", mTextWrapScale); + b.putBoolean("overview", mInZoomOverview); + return true; + } + + private void restoreHistoryPictureFields(Picture p, Bundle b) { + int sx = b.getInt("scrollX", 0); + int sy = b.getInt("scrollY", 0); + float scale = b.getFloat("scale", 1.0f); + mDrawHistory = true; + mHistoryPicture = p; + mScrollX = sx; + mScrollY = sy; + mHistoryWidth = Math.round(p.getWidth() * scale); + mHistoryHeight = Math.round(p.getHeight() * scale); + // as getWidth() / getHeight() of the view are not available yet, set up + // mActualScale, so that when onSizeChanged() is called, the rest will + // be set correctly + mActualScale = scale; + mInvActualScale = 1 / scale; + mTextWrapScale = b.getFloat("textwrapScale", scale); + mInZoomOverview = b.getBoolean("overview"); + invalidate(); } /** @@ -1311,42 +1339,35 @@ public class WebView extends AbsoluteLayout if (src == null || b == null) { return false; } - if (src.exists()) { - Picture p = null; - try { - final FileInputStream in = new FileInputStream(src); - p = Picture.createFromStream(in); - in.close(); - } catch (FileNotFoundException e){ - e.printStackTrace(); - } catch (RuntimeException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - if (p != null) { - int sx = b.getInt("scrollX", 0); - int sy = b.getInt("scrollY", 0); - float scale = b.getFloat("scale", 1.0f); - mDrawHistory = true; - mHistoryPicture = p; - mScrollX = sx; - mScrollY = sy; - mHistoryWidth = Math.round(p.getWidth() * scale); - mHistoryHeight = Math.round(p.getHeight() * scale); - // as getWidth() / getHeight() of the view are not - // available yet, set up mActualScale, so that when - // onSizeChanged() is called, the rest will be set - // correctly - mActualScale = scale; - mInvActualScale = 1 / scale; - mTextWrapScale = b.getFloat("textwrapScale", scale); - mInZoomOverview = b.getBoolean("overview"); - invalidate(); - return true; - } + if (!src.exists()) { + return false; } - return false; + try { + final FileInputStream in = new FileInputStream(src); + final Bundle copy = new Bundle(b); + new Thread(new Runnable() { + public void run() { + final Picture p = Picture.createFromStream(in); + if (p != null) { + // Post a runnable on the main thread to update the + // history picture fields. + mPrivateHandler.post(new Runnable() { + public void run() { + restoreHistoryPictureFields(p, copy); + } + }); + } + try { + in.close(); + } catch (Exception e) { + // Nothing we can do now. + } + } + }).start(); + } catch (FileNotFoundException e){ + e.printStackTrace(); + } + return true; } /** @@ -3339,6 +3360,7 @@ public class WebView extends AbsoluteLayout if (null == mWebViewCore) return; // CallbackProxy may trigger this if (mDrawHistory && mWebViewCore.pictureReady()) { mDrawHistory = false; + mHistoryPicture = null; invalidate(); int oldScrollX = mScrollX; int oldScrollY = mScrollY; @@ -4902,9 +4924,6 @@ public class WebView extends AbsoluteLayout case TOUCH_DRAG_MODE: mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); - mHeldMotionless = MOTIONLESS_TRUE; - // redraw in high-quality, as we're done dragging - invalidate(); // if the user waits a while w/o moving before the // up, we don't want to do a fling if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { @@ -4916,9 +4935,16 @@ public class WebView extends AbsoluteLayout + mDeferTouchProcess); } mVelocityTracker.addMovement(ev); + // set to MOTIONLESS_IGNORE so that it won't keep + // removing and sending message in + // drawCoreAndCursorRing() + mHeldMotionless = MOTIONLESS_IGNORE; doFling(); break; } + // redraw in high-quality, as we're done dragging + mHeldMotionless = MOTIONLESS_TRUE; + invalidate(); // fall through case TOUCH_DRAG_START_MODE: // TOUCH_DRAG_START_MODE should not happen for the real diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 873dc67..959e982 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -330,7 +330,7 @@ public class ScrollView extends FrameLayout { mTempRect.setEmpty(); if (!canScroll()) { - if (isFocused()) { + if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) { View currentFocused = findFocus(); if (currentFocused == this) currentFocused = null; View nextFocused = FocusFinder.getInstance().findNextFocus(this, diff --git a/core/java/android/net/http/HttpDateTime.java b/core/java/com/android/internal/http/HttpDateTime.java index c7a31ee..8ebd4aa 100644 --- a/core/java/android/net/http/HttpDateTime.java +++ b/core/java/com/android/internal/http/HttpDateTime.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.net.http; +package com.android.internal.http; import android.text.format.Time; @@ -82,7 +82,7 @@ public final class HttpDateTime { int second; } - public static Long parse(String timeString) + public static long parse(String timeString) throws IllegalArgumentException { int date = 1; diff --git a/core/jni/android_net_TrafficStats.cpp b/core/jni/android_net_TrafficStats.cpp index db8fdf2..ff46bdd 100644 --- a/core/jni/android_net_TrafficStats.cpp +++ b/core/jni/android_net_TrafficStats.cpp @@ -32,6 +32,7 @@ namespace android { // Returns an ASCII decimal number read from the specified file, -1 on error. static jlong readNumber(char const* filename) { +#ifdef HAVE_ANDROID_OS char buf[80]; int fd = open(filename, O_RDONLY); if (fd < 0) { @@ -49,6 +50,9 @@ static jlong readNumber(char const* filename) { close(fd); buf[len] = '\0'; return atoll(buf); +#else // Simulator + return -1; +#endif } // Return the number from the first file which exists and contains data @@ -60,6 +64,7 @@ static jlong tryBoth(char const* a, char const* b) { // Returns the sum of numbers from the specified path under /sys/class/net/*, // -1 if no such file exists. static jlong readTotal(char const* suffix) { +#ifdef HAVE_ANDROID_OS char filename[PATH_MAX] = "/sys/class/net/"; DIR *dir = opendir(filename); if (dir == NULL) { @@ -81,6 +86,9 @@ static jlong readTotal(char const* suffix) { closedir(dir); return total; +#else // Simulator + return -1; +#endif } // Mobile stats get accessed a lot more often than total stats. diff --git a/core/jni/android_util_EventLog.cpp b/core/jni/android_util_EventLog.cpp index 75f6cb2..e43478c 100644 --- a/core/jni/android_util_EventLog.cpp +++ b/core/jni/android_util_EventLog.cpp @@ -165,14 +165,32 @@ static void android_util_EventLog_readEvents(JNIEnv* env, jobject clazz, jint *tagValues = env->GetIntArrayElements(tags, NULL); uint8_t buf[LOGGER_ENTRY_MAX_LEN]; + struct timeval timeout = {0, 0}; + fd_set readset; + FD_ZERO(&readset); + for (;;) { + // Use a short select() to try to avoid problems hanging on read(). + // This means we block for 5ms at the end of the log -- oh well. + timeout.tv_usec = 5000; + FD_SET(fd, &readset); + int r = select(fd + 1, &readset, NULL, NULL, &timeout); + if (r == 0) { + break; // no more events + } else if (r < 0 && errno == EINTR) { + continue; // interrupted by signal, try again + } else if (r < 0) { + jniThrowIOException(env, errno); // Will throw on return + break; + } + int len = read(fd, buf, sizeof(buf)); if (len == 0 || (len < 0 && errno == EAGAIN)) { - break; + break; // no more events + } else if (len < 0 && errno == EINTR) { + continue; // interrupted by signal, try again } else if (len < 0) { - // This calls env->ThrowNew(), which doesn't throw an exception - // now, but sets a flag to trigger an exception after we return. - jniThrowIOException(env, errno); + jniThrowIOException(env, errno); // Will throw on return break; } else if ((size_t) len < sizeof(logger_entry) + sizeof(int32_t)) { jniThrowException(env, "java/io/IOException", "Event too short"); diff --git a/core/res/res/drawable-mdpi/stat_notify_chat.png b/core/res/res/drawable-mdpi/stat_notify_chat.png Binary files differindex 097a979..068b7ec 100644 --- a/core/res/res/drawable-mdpi/stat_notify_chat.png +++ b/core/res/res/drawable-mdpi/stat_notify_chat.png diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 36dc07c..45d7aea 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2260,11 +2260,11 @@ <!-- Strings for throttling notification --> <!-- Shown when the user is in danger of being throttled --> - <string name="throttle_warning_notification_title">Excessive data use warning</string> - <string name="throttle_warning_notification_message">If your data use pattern continues you may be subject to bandwidth restrictions - touch for more information</string> + <string name="throttle_warning_notification_title">High mobile data use</string> + <string name="throttle_warning_notification_message">Touch to learn more about mobile data use</string> <!-- Strings for throttling notification --> <!-- Shown when the users bandwidth is reduced because of excessive data use --> - <string name="throttled_notification_title">Bandwidth Restricted</string> - <string name="throttled_notification_message">Your mobile data bandwidth is being reduced because of excessive data use - touch for more information</string> + <string name="throttled_notification_title">Mobile data limit exceeded</string> + <string name="throttled_notification_message">Touch to learn more about mobile data use</string> </resources> diff --git a/data/fonts/DroidSansArabic.ttf b/data/fonts/DroidSansArabic.ttf Binary files differindex c179bc6..660e2a9 100644 --- a/data/fonts/DroidSansArabic.ttf +++ b/data/fonts/DroidSansArabic.ttf diff --git a/data/fonts/DroidSansHebrew.ttf b/data/fonts/DroidSansHebrew.ttf Binary files differindex 0b48628..8d77e3e 100644 --- a/data/fonts/DroidSansHebrew.ttf +++ b/data/fonts/DroidSansHebrew.ttf diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp index 7166c89..2414e8d 100644 --- a/libs/audioflinger/AudioFlinger.cpp +++ b/libs/audioflinger/AudioFlinger.cpp @@ -1867,7 +1867,7 @@ bool AudioFlinger::DirectOutputThread::threadLoop() // The first time a track is added we wait // for all its buffers to be filled before processing it if (cblk->framesReady() && (track->isReady() || track->isStopped()) && - !track->isPaused()) + !track->isPaused() && !track->isTerminated()) { //LOGV("track %d u=%08x, s=%08x [OK]", track->name(), cblk->user, cblk->server); diff --git a/media/libstagefright/id3/ID3.cpp b/media/libstagefright/id3/ID3.cpp index d688e2c..ca0c68c 100644 --- a/media/libstagefright/id3/ID3.cpp +++ b/media/libstagefright/id3/ID3.cpp @@ -58,13 +58,27 @@ ID3::Version ID3::version() const { return mVersion; } +// static +bool ID3::ParseSyncsafeInteger(const uint8_t encoded[4], size_t *x) { + *x = 0; + for (int32_t i = 0; i < 4; ++i) { + if (encoded[i] & 0x80) { + return false; + } + + *x = ((*x) << 7) | encoded[i]; + } + + return true; +} + bool ID3::parseV2(const sp<DataSource> &source) { - struct id3_header { - char id[3]; - uint8_t version_major; - uint8_t version_minor; - uint8_t flags; - uint8_t enc_size[4]; +struct id3_header { + char id[3]; + uint8_t version_major; + uint8_t version_minor; + uint8_t flags; + uint8_t enc_size[4]; }; id3_header header; @@ -100,17 +114,18 @@ bool ID3::parseV2(const sp<DataSource> &source) { // set, we cannot guarantee to understand the tag format. return false; } + } else if (header.version_major == 4) { + if (header.flags & 0x0f) { + // The lower 4 bits are undefined in this spec. + return false; + } } else { return false; } - size_t size = 0; - for (int32_t i = 0; i < 4; ++i) { - if (header.enc_size[i] & 0x80) { - return false; - } - - size = (size << 7) | header.enc_size[i]; + size_t size; + if (!ParseSyncsafeInteger(header.enc_size, &size)) { + return false; } if (size > kMaxMetadataSize) { @@ -178,13 +193,42 @@ bool ID3::parseV2(const sp<DataSource> &source) { LOGV("have crc"); } } + } else if (header.version_major == 4 && (header.flags & 0x40)) { + // Version 2.4 has an optional extended header, that's different + // from Version 2.3's... + + if (mSize < 4) { + free(mData); + mData = NULL; + + return false; + } + + size_t ext_size; + if (!ParseSyncsafeInteger(mData, &ext_size)) { + free(mData); + mData = NULL; + + return false; + } + + if (ext_size < 6 || ext_size > mSize) { + free(mData); + mData = NULL; + + return false; + } + + mFirstFrameOffset = ext_size; } if (header.version_major == 2) { mVersion = ID3_V2_2; - } else { - CHECK_EQ(header.version_major, 3); + } else if (header.version_major == 3) { mVersion = ID3_V2_3; + } else { + CHECK_EQ(header.version_major, 4); + mVersion = ID3_V2_4; } return true; @@ -242,7 +286,7 @@ void ID3::Iterator::getID(String8 *id) const { if (mParent.mVersion == ID3_V2_2) { id->setTo((const char *)&mParent.mData[mOffset], 3); - } else if (mParent.mVersion == ID3_V2_3) { + } else if (mParent.mVersion == ID3_V2_3 || mParent.mVersion == ID3_V2_4) { id->setTo((const char *)&mParent.mData[mOffset], 4); } else { CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1); @@ -346,6 +390,26 @@ void ID3::Iterator::getString(String8 *id) const { if (*mFrameData == 0x00) { // ISO 8859-1 convertISO8859ToString8(mFrameData + 1, n, id); + } else if (*mFrameData == 0x03) { + // UTF-8 + id->setTo((const char *)(mFrameData + 1), n); + } else if (*mFrameData == 0x02) { + // UTF-16 BE, no byte order mark. + // API wants number of characters, not number of bytes... + int len = n / 2; + const char16_t *framedata = (const char16_t *) (mFrameData + 1); + char16_t *framedatacopy = NULL; +#if BYTE_ORDER == LITTLE_ENDIAN + framedatacopy = new char16_t[len]; + for (int i = 0; i < len; i++) { + framedatacopy[i] = bswap_16(framedata[i]); + } + framedata = framedatacopy; +#endif + id->setTo(framedata, len); + if (framedatacopy != NULL) { + delete[] framedatacopy; + } } else { // UCS-2 // API wants number of characters, not number of bytes... @@ -387,7 +451,7 @@ const uint8_t *ID3::Iterator::getData(size_t *length) const { size_t ID3::Iterator::getHeaderLength() const { if (mParent.mVersion == ID3_V2_2) { return 6; - } else if (mParent.mVersion == ID3_V2_3) { + } else if (mParent.mVersion == ID3_V2_3 || mParent.mVersion == ID3_V2_4) { return 10; } else { CHECK(mParent.mVersion == ID3_V1 || mParent.mVersion == ID3_V1_1); @@ -435,7 +499,8 @@ void ID3::Iterator::findFrame() { if (!strcmp(id, mID)) { break; } - } else if (mParent.mVersion == ID3_V2_3) { + } else if (mParent.mVersion == ID3_V2_3 + || mParent.mVersion == ID3_V2_4) { if (mOffset + 10 > mParent.mSize) { return; } @@ -444,7 +509,17 @@ void ID3::Iterator::findFrame() { return; } - mFrameSize = 10 + U32_AT(&mParent.mData[mOffset + 4]); + size_t baseSize; + if (mParent.mVersion == ID3_V2_4) { + if (!ParseSyncsafeInteger( + &mParent.mData[mOffset + 4], &baseSize)) { + return; + } + } else { + baseSize = U32_AT(&mParent.mData[mOffset + 4]); + } + + mFrameSize = 10 + baseSize; if (mOffset + mFrameSize > mParent.mSize) { LOGV("partial frame at offset %d (size = %d, bytes-remaining = %d)", @@ -452,6 +527,20 @@ void ID3::Iterator::findFrame() { return; } + uint16_t flags = U16_AT(&mParent.mData[mOffset + 8]); + + if ((mParent.mVersion == ID3_V2_4 && (flags & 0x000e)) + || (mParent.mVersion == ID3_V2_3 && (flags & 0x00c0))) { + // Compression, Encryption or per-Frame unsynchronization + // are not supported at this time. + + LOGV("Skipping unsupported frame (compression, encryption " + "or per-frame unsynchronization flagged"); + + mOffset += mFrameSize; + continue; + } + mFrameData = &mParent.mData[mOffset + 10]; if (!mID) { @@ -518,8 +607,8 @@ void ID3::Iterator::findFrame() { } static size_t StringSize(const uint8_t *start, uint8_t encoding) { - if (encoding== 0x00) { - // ISO 8859-1 + if (encoding == 0x00 || encoding == 0x03) { + // ISO 8859-1 or UTF-8 return strlen((const char *)start) + 1; } @@ -537,13 +626,15 @@ ID3::getAlbumArt(size_t *length, String8 *mime) const { *length = 0; mime->setTo(""); - Iterator it(*this, mVersion == ID3_V2_3 ? "APIC" : "PIC"); + Iterator it( + *this, + (mVersion == ID3_V2_3 || mVersion == ID3_V2_4) ? "APIC" : "PIC"); while (!it.done()) { size_t size; const uint8_t *data = it.getData(&size); - if (mVersion == ID3_V2_3) { + if (mVersion == ID3_V2_3 || mVersion == ID3_V2_4) { uint8_t encoding = data[0]; mime->setTo((const char *)&data[1]); size_t mimeLen = strlen((const char *)&data[1]) + 1; diff --git a/media/libstagefright/include/ID3.h b/media/libstagefright/include/ID3.h index da042a3..c6b1a8b 100644 --- a/media/libstagefright/include/ID3.h +++ b/media/libstagefright/include/ID3.h @@ -31,7 +31,8 @@ struct ID3 { ID3_V1, ID3_V1_1, ID3_V2_2, - ID3_V2_3 + ID3_V2_3, + ID3_V2_4, }; ID3(const sp<DataSource> &source); @@ -80,6 +81,8 @@ private: bool parseV2(const sp<DataSource> &source); void removeUnsynchronization(); + static bool ParseSyncsafeInteger(const uint8_t encoded[4], size_t *x); + ID3(const ID3 &); ID3 &operator=(const ID3 &); }; diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java index ee6067a..23bbb87 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaPlayerApiTest.java @@ -439,7 +439,7 @@ public class MediaPlayerApiTest extends ActivityInstrumentationTestCase<MediaFra @MediumTest public void testPrepareAsyncReset() throws Exception { - assertTrue(MediaFrameworkTest.checkStreamingServer()); + //assertTrue(MediaFrameworkTest.checkStreamingServer()); boolean isReset = CodecTest.prepareAsyncReset(MediaNames.STREAM_MP3); assertTrue("PrepareAsync Reset", isReset); } @@ -472,7 +472,7 @@ public class MediaPlayerApiTest extends ActivityInstrumentationTestCase<MediaFra @LargeTest public void testStreamPrepareAsyncCallback() throws Exception { - assertTrue(MediaFrameworkTest.checkStreamingServer()); + //assertTrue(MediaFrameworkTest.checkStreamingServer()); boolean onPrepareSuccess = CodecTest.prepareAsyncCallback(MediaNames.STREAM_H264_480_360_1411k, false); assertTrue("StreamH264PrepareAsyncCallback", onPrepareSuccess); @@ -480,7 +480,7 @@ public class MediaPlayerApiTest extends ActivityInstrumentationTestCase<MediaFra @LargeTest public void testStreamPrepareAsyncCallbackReset() throws Exception { - assertTrue(MediaFrameworkTest.checkStreamingServer()); + //assertTrue(MediaFrameworkTest.checkStreamingServer()); boolean onPrepareSuccess = CodecTest.prepareAsyncCallback(MediaNames.STREAM_H264_480_360_1411k, true); assertTrue("StreamH264PrepareAsyncCallback", onPrepareSuccess); diff --git a/preloaded-classes b/preloaded-classes index 54c7303..8114562 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -313,7 +313,7 @@ android.net.http.AndroidHttpClientConnection android.net.http.EventHandler android.net.http.Headers android.net.http.HttpsConnection -android.net.http.HttpDateTime +com.android.internal.http.HttpDateTime android.net.http.Request android.net.http.RequestQueue android.net.http.SslCertificate diff --git a/services/java/com/android/server/PackageManagerBackupAgent.java b/services/java/com/android/server/PackageManagerBackupAgent.java index 26b57bf..77bddb0 100644 --- a/services/java/com/android/server/PackageManagerBackupAgent.java +++ b/services/java/com/android/server/PackageManagerBackupAgent.java @@ -122,8 +122,8 @@ public class PackageManagerBackupAgent extends BackupAgent { ParcelFileDescriptor newState) { if (DEBUG) Slog.v(TAG, "onBackup()"); - ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); // we'll reuse these - DataOutputStream outWriter = new DataOutputStream(bufStream); + ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream(); // we'll reuse these + DataOutputStream outputBufferStream = new DataOutputStream(outputBuffer); parseStateFile(oldState); // If the stored version string differs, we need to re-backup all @@ -148,11 +148,9 @@ public class PackageManagerBackupAgent extends BackupAgent { */ if (!mExisting.contains(GLOBAL_METADATA_KEY)) { if (DEBUG) Slog.v(TAG, "Storing global metadata key"); - outWriter.writeInt(Build.VERSION.SDK_INT); - outWriter.writeUTF(Build.VERSION.INCREMENTAL); - byte[] metadata = bufStream.toByteArray(); - data.writeEntityHeader(GLOBAL_METADATA_KEY, metadata.length); - data.writeEntityData(metadata, metadata.length); + outputBufferStream.writeInt(Build.VERSION.SDK_INT); + outputBufferStream.writeUTF(Build.VERSION.INCREMENTAL); + writeEntity(data, GLOBAL_METADATA_KEY, outputBuffer.toByteArray()); } else { if (DEBUG) Slog.v(TAG, "Global metadata key already stored"); // don't consider it to have been skipped/deleted @@ -178,49 +176,46 @@ public class PackageManagerBackupAgent extends BackupAgent { continue; } - boolean doBackup = false; - if (!mExisting.contains(packName)) { - // We haven't backed up this app before - doBackup = true; - } else { - // We *have* backed this one up before. Check whether the version + if (mExisting.contains(packName)) { + // We have backed up this app before. Check whether the version // of the backup matches the version of the current app; if they // don't match, the app has been updated and we need to store its // metadata again. In either case, take it out of mExisting so that // we don't consider it deleted later. - if (info.versionCode != mStateVersions.get(packName).versionCode) { - doBackup = true; - } mExisting.remove(packName); + if (info.versionCode == mStateVersions.get(packName).versionCode) { + continue; + } + } + + if (info.signatures == null || info.signatures.length == 0) + { + Slog.w(TAG, "Not backing up package " + packName + + " since it appears to have no signatures."); + continue; } - if (doBackup) { - // We need to store this app's metadata - /* - * Metadata for each package: - * - * int version -- [4] the package's versionCode - * byte[] signatures -- [len] flattened Signature[] of the package - */ - - // marshal the version code in a canonical form - bufStream.reset(); - outWriter.writeInt(info.versionCode); - byte[] versionBuf = bufStream.toByteArray(); - - byte[] sigs = flattenSignatureArray(info.signatures); - - if (DEBUG) { - Slog.v(TAG, "+ metadata for " + packName - + " version=" + info.versionCode - + " versionLen=" + versionBuf.length - + " sigsLen=" + sigs.length); - } - // Now we can write the backup entity for this package - data.writeEntityHeader(packName, versionBuf.length + sigs.length); - data.writeEntityData(versionBuf, versionBuf.length); - data.writeEntityData(sigs, sigs.length); + // We need to store this app's metadata + /* + * Metadata for each package: + * + * int version -- [4] the package's versionCode + * byte[] signatures -- [len] flattened Signature[] of the package + */ + + // marshal the version code in a canonical form + outputBuffer.reset(); + outputBufferStream.writeInt(info.versionCode); + writeSignatureArray(outputBufferStream, info.signatures); + + if (DEBUG) { + Slog.v(TAG, "+ writing metadata for " + packName + + " version=" + info.versionCode + + " entityLen=" + outputBuffer.size()); } + + // Now we can write the backup entity for this package + writeEntity(data, packName, outputBuffer.toByteArray()); } } @@ -245,6 +240,12 @@ public class PackageManagerBackupAgent extends BackupAgent { // Finally, write the new state blob -- just the list of all apps we handled writeStateFile(mAllPackages, newState); } + + private static void writeEntity(BackupDataOutput data, String key, byte[] bytes) + throws IOException { + data.writeEntityHeader(key, bytes.length); + data.writeEntityData(bytes, bytes.length); + } // "Restore" here is a misnomer. What we're really doing is reading back the // set of app signatures associated with each backed-up app in this restore @@ -263,13 +264,13 @@ public class PackageManagerBackupAgent extends BackupAgent { if (DEBUG) Slog.v(TAG, " got key=" + key + " dataSize=" + dataSize); // generic setup to parse any entity data - byte[] dataBuf = new byte[dataSize]; - data.readEntityData(dataBuf, 0, dataSize); - ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf); - DataInputStream in = new DataInputStream(baStream); + byte[] inputBytes = new byte[dataSize]; + data.readEntityData(inputBytes, 0, dataSize); + ByteArrayInputStream inputBuffer = new ByteArrayInputStream(inputBytes); + DataInputStream inputBufferStream = new DataInputStream(inputBuffer); if (key.equals(GLOBAL_METADATA_KEY)) { - int storedSdkVersion = in.readInt(); + int storedSdkVersion = inputBufferStream.readInt(); if (DEBUG) Slog.v(TAG, " storedSystemVersion = " + storedSystemVersion); if (storedSystemVersion > Build.VERSION.SDK_INT) { // returning before setting the sig map means we rejected the restore set @@ -277,7 +278,7 @@ public class PackageManagerBackupAgent extends BackupAgent { return; } mStoredSdkVersion = storedSdkVersion; - mStoredIncrementalVersion = in.readUTF(); + mStoredIncrementalVersion = inputBufferStream.readUTF(); mHasMetadata = true; if (DEBUG) { Slog.i(TAG, "Restore set version " + storedSystemVersion @@ -287,13 +288,19 @@ public class PackageManagerBackupAgent extends BackupAgent { } } else { // it's a file metadata record - int versionCode = in.readInt(); - Signature[] sigs = unflattenSignatureArray(in); + int versionCode = inputBufferStream.readInt(); + Signature[] sigs = readSignatureArray(inputBufferStream); if (DEBUG) { - Slog.i(TAG, " restored metadata for " + key + Slog.i(TAG, " read metadata for " + key + " dataSize=" + dataSize + " versionCode=" + versionCode + " sigs=" + sigs); } + + if (sigs == null || sigs.length == 0) { + Slog.w(TAG, "Not restoring package " + key + + " since it appears to have no signatures."); + continue; + } ApplicationInfo app = new ApplicationInfo(); app.packageName = key; @@ -306,63 +313,50 @@ public class PackageManagerBackupAgent extends BackupAgent { mRestoredSignatures = sigMap; } - - // Util: convert an array of Signatures into a flattened byte buffer. The - // flattened format contains enough info to reconstruct the signature array. - private byte[] flattenSignatureArray(Signature[] allSigs) { - ByteArrayOutputStream outBuf = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(outBuf); - - // build the set of subsidiary buffers - try { - // first the # of signatures in the array - out.writeInt(allSigs.length); - - // then the signatures themselves, length + flattened buffer - for (Signature sig : allSigs) { - byte[] flat = sig.toByteArray(); - out.writeInt(flat.length); - out.write(flat); - } - } catch (IOException e) { - // very strange; we're writing to memory here. abort. - return null; + private static void writeSignatureArray(DataOutputStream out, Signature[] sigs) + throws IOException { + // write the number of signatures in the array + out.writeInt(sigs.length); + + // write the signatures themselves, length + flattened buffer + for (Signature sig : sigs) { + byte[] flat = sig.toByteArray(); + out.writeInt(flat.length); + out.write(flat); } - - return outBuf.toByteArray(); } - private Signature[] unflattenSignatureArray(/*byte[] buffer*/ DataInputStream in) { - Signature[] sigs = null; - + private static Signature[] readSignatureArray(DataInputStream in) { try { - int num = in.readInt(); + int num; + try { + num = in.readInt(); + } catch (EOFException e) { + // clean termination + Slog.w(TAG, "Read empty signature block"); + return null; + } + if (DEBUG) Slog.v(TAG, " ... unflatten read " + num); - + // Sensical? if (num > 20) { Slog.e(TAG, "Suspiciously large sig count in restore data; aborting"); throw new IllegalStateException("Bad restore state"); } - - sigs = new Signature[num]; + + Signature[] sigs = new Signature[num]; for (int i = 0; i < num; i++) { int len = in.readInt(); byte[] flatSig = new byte[len]; in.read(flatSig); sigs[i] = new Signature(flatSig); } - } catch (EOFException e) { - // clean termination - if (sigs == null) { - Slog.w(TAG, "Empty signature block found"); - } + return sigs; } catch (IOException e) { - Slog.e(TAG, "Unable to unflatten sigs"); + Slog.e(TAG, "Unable to read signatures"); return null; } - - return sigs; } // Util: parse out an existing state file into a usable structure diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index e6663d4..3908389 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -2496,16 +2496,25 @@ class PackageManagerService extends IPackageManager.Stub { private boolean collectCertificatesLI(PackageParser pp, PackageSetting ps, PackageParser.Package pkg, File srcFile, int parseFlags) { if (GET_CERTIFICATES) { - if (ps == null || !ps.codePath.equals(srcFile) - || ps.getTimeStamp() != srcFile.lastModified()) { - Log.i(TAG, srcFile.toString() + " changed; collecting certs"); - if (!pp.collectCertificates(pkg, parseFlags)) { - mLastScanError = pp.getParseError(); - return false; + if (ps != null + && ps.codePath.equals(srcFile) + && ps.getTimeStamp() == srcFile.lastModified()) { + if (ps.signatures.mSignatures != null + && ps.signatures.mSignatures.length != 0) { + // Optimization: reuse the existing cached certificates + // if the package appears to be unchanged. + pkg.mSignatures = ps.signatures.mSignatures; + return true; } + + Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them."); } else { - // Lets implicitly assign existing certificates. - pkg.mSignatures = ps.signatures.mSignatures; + Log.i(TAG, srcFile.toString() + " changed; collecting certs"); + } + + if (!pp.collectCertificates(pkg, parseFlags)) { + mLastScanError = pp.getParseError(); + return false; } } return true; diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index 36931b2..9d4e226 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -22,12 +22,14 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.SharedPreferences; +import android.database.ContentObserver; import android.net.IThrottleManager; import android.net.ThrottleManager; import android.os.Binder; @@ -44,6 +46,8 @@ import android.os.SystemProperties; import android.provider.Settings; import android.util.Slog; +import com.android.internal.telephony.TelephonyProperties; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Calendar; @@ -96,8 +100,6 @@ public class ThrottleService extends IThrottleManager.Stub { private DataRecorder mRecorder; - private int mThrottleLevel; // 0 for none, 1 for first throttle val, 2 for next, etc - private String mPolicyIface; private static final int NOTIFICATION_WARNING = 2; @@ -107,6 +109,12 @@ public class ThrottleService extends IThrottleManager.Stub { private Notification mThrottlingNotification; private boolean mWarningNotificationSent = false; + private SettingsObserver mSettingsObserver; + + private int mThrottleIndex; // 0 for none, 1 for first throttle val, 2 for next, etc + private static final int THROTTLE_INDEX_UNINITIALIZED = -1; + private static final int THROTTLE_INDEX_UNTHROTTLED = 0; + public ThrottleService(Context context) { if (DBG) Slog.d(TAG, "Starting ThrottleService"); mContext = context; @@ -124,6 +132,38 @@ public class ThrottleService extends IThrottleManager.Stub { Context.NOTIFICATION_SERVICE); } + private static class SettingsObserver extends ContentObserver { + private int mMsg; + private Handler mHandler; + SettingsObserver(Handler handler, int msg) { + super(handler); + mHandler = handler; + mMsg = msg; + } + + void observe(Context context) { + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.THROTTLE_POLLING_SEC), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.THROTTLE_THRESHOLD), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.THROTTLE_VALUE), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.THROTTLE_RESET_DAY), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.THROTTLE_NOTIFICATION_TYPE), false, this); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.THROTTLE_IFACE), false, this); + // TODO - add help url + } + + @Override + public void onChange(boolean selfChange) { + mHandler.obtainMessage(mMsg).sendToTarget(); + } + } + private void enforceAccessPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, @@ -143,7 +183,7 @@ public class ThrottleService extends IThrottleManager.Stub { //TODO - a better name? getCliffByteCountThreshold? public synchronized long getCliffThreshold(String iface, int cliff) { enforceAccessPermission(); - if ((cliff == 0) && iface.equals(mPolicyIface)) { + if ((cliff == 1) && iface.equals(mPolicyIface)) { return mPolicyThreshold; } return 0; @@ -151,12 +191,18 @@ public class ThrottleService extends IThrottleManager.Stub { // TODO - a better name? getThrottleRate? public synchronized int getCliffLevel(String iface, int cliff) { enforceAccessPermission(); - if ((cliff == 0) && iface.equals(mPolicyIface)) { + if ((cliff == 1) && iface.equals(mPolicyIface)) { return mPolicyThrottleValue; } return 0; } + public String getHelpUri() { + enforceAccessPermission(); + return Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.THROTTLE_HELP_URI); + } + public synchronized long getByteCount(String iface, int dir, int period, int ago) { enforceAccessPermission(); if (iface.equals(mPolicyIface) && @@ -171,7 +217,7 @@ public class ThrottleService extends IThrottleManager.Stub { // TODO - a better name - getCurrentThrottleRate? public synchronized int getThrottle(String iface) { enforceAccessPermission(); - if (iface.equals(mPolicyIface) && (mThrottleLevel == 1)) { + if (iface.equals(mPolicyIface) && (mThrottleIndex == 1)) { return mPolicyThrottleValue; } return 0; @@ -200,6 +246,9 @@ public class ThrottleService extends IThrottleManager.Stub { mThread.start(); mHandler = new MyHandler(mThread.getLooper()); mHandler.obtainMessage(EVENT_REBOOT_RECOVERY).sendToTarget(); + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_POLICY_CHANGED); + mSettingsObserver.observe(mContext); } @@ -234,8 +283,7 @@ public class ThrottleService extends IThrottleManager.Stub { // check for sim change TODO // reregister for notification of policy change - // register for roaming indication change - // check for roaming TODO + mThrottleIndex = THROTTLE_INDEX_UNINITIALIZED; mRecorder = new DataRecorder(mContext, ThrottleService.this); @@ -292,14 +340,10 @@ public class ThrottleService extends IThrottleManager.Stub { ", resetDay=" + mPolicyResetDay + ", noteType=" + mPolicyNotificationsAllowedMask); - Calendar end = calculatePeriodEnd(); - Calendar start = calculatePeriodStart(end); + onResetAlarm(); - mRecorder.setNextPeriod(start,end); - - mAlarmManager.cancel(mPendingResetIntent); - mAlarmManager.set(AlarmManager.RTC_WAKEUP, end.getTimeInMillis(), - mPendingResetIntent); + Intent broadcast = new Intent(ThrottleManager.POLICY_CHANGED_ACTION); + mContext.sendBroadcast(broadcast); } private void onPollAlarm() { @@ -313,15 +357,19 @@ public class ThrottleService extends IThrottleManager.Stub { } catch (RemoteException e) { Slog.e(TAG, "got remoteException in onPollAlarm:" + e); } - - mRecorder.addData(incRead, incWrite); + // don't count this data if we're roaming. + boolean roaming = "true".equals( + SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING)); + if (!roaming) { + mRecorder.addData(incRead, incWrite); + } long periodRx = mRecorder.getPeriodRx(0); long periodTx = mRecorder.getPeriodTx(0); long total = periodRx + periodTx; if (DBG) { - Slog.d(TAG, "onPollAlarm - now =" + now + ", read =" + incRead + - ", written =" + incWrite + ", new total =" + total); + Slog.d(TAG, "onPollAlarm - now =" + now + ", roaming =" + roaming + + ", read =" + incRead + ", written =" + incWrite + ", new total =" + total); } mLastRead += incRead; mLastWrite += incWrite; @@ -345,9 +393,9 @@ public class ThrottleService extends IThrottleManager.Stub { // check if we need to throttle if (currentTotal > mPolicyThreshold) { - if (mThrottleLevel != 1) { + if (mThrottleIndex != 1) { synchronized (ThrottleService.this) { - mThrottleLevel = 1; + mThrottleIndex = 1; } if (DBG) Slog.d(TAG, "Threshold " + mPolicyThreshold + " exceeded!"); try { @@ -362,7 +410,8 @@ public class ThrottleService extends IThrottleManager.Stub { postNotification(com.android.internal.R.string.throttled_notification_title, com.android.internal.R.string.throttled_notification_message, - com.android.internal.R.drawable.stat_sys_throttled); + com.android.internal.R.drawable.stat_sys_throttled, + Notification.FLAG_ONGOING_EVENT); Intent broadcast = new Intent(ThrottleManager.THROTTLE_ACTION); broadcast.putExtra(ThrottleManager.EXTRA_THROTTLE_LEVEL, mPolicyThrottleValue); @@ -372,23 +421,46 @@ public class ThrottleService extends IThrottleManager.Stub { } else { if ((mPolicyNotificationsAllowedMask & NOTIFICATION_WARNING) != 0) { // check if we should warn about throttle - if (currentTotal > (mPolicyThreshold/2) && !mWarningNotificationSent) { - mWarningNotificationSent = true; - mNotificationManager.cancel(com.android.internal.R.drawable. - stat_sys_throttle_warning); - postNotification(com.android.internal.R.string. - throttle_warning_notification_title, - com.android.internal.R.string. - throttle_warning_notification_message, - com.android.internal.R.drawable.stat_sys_throttle_warning); + // pretend we only have 1/2 the time remaining that we actually do + // if our burn rate in the period so far would have us exceed the limit + // in that 1/2 window, warn the user. + // this gets more generous in the early to middle period and converges back + // to the limit as we move toward the period end. + + // adding another factor - it must be greater than the total cap/4 + // else we may get false alarms very early in the period.. in the first + // tenth of a percent of the period if we used more than a tenth of a percent + // of the cap we'd get a warning and that's not desired. + long start = mRecorder.getPeriodStart(); + long end = mRecorder.getPeriodEnd(); + long periodLength = end - start; + long now = System.currentTimeMillis(); + long timeUsed = now - start; + long warningThreshold = 2*mPolicyThreshold*timeUsed/(timeUsed+periodLength); + if ((currentTotal > warningThreshold) && (currentTotal > mPolicyThreshold/4)) { + if (mWarningNotificationSent == false) { + mWarningNotificationSent = true; + mNotificationManager.cancel(com.android.internal.R.drawable. + stat_sys_throttle_warning); + postNotification(com.android.internal.R.string. + throttle_warning_notification_title, + com.android.internal.R.string. + throttle_warning_notification_message, + com.android.internal.R.drawable.stat_sys_throttle_warning, + 0); + } } else { - mWarningNotificationSent =false; + if (mWarningNotificationSent == true) { + mNotificationManager.cancel(com.android.internal.R.drawable. + stat_sys_throttle_warning); + mWarningNotificationSent =false; + } } } } } - private void postNotification(int titleInt, int messageInt, int icon) { + private void postNotification(int titleInt, int messageInt, int icon, int flags) { Intent intent = new Intent(); // TODO - fix up intent intent.setClassName("com.android.settings", "com.android.settings.TetherSettings"); @@ -405,8 +477,8 @@ public class ThrottleService extends IThrottleManager.Stub { // TODO - fixup icon mThrottlingNotification.icon = icon; mThrottlingNotification.defaults &= ~Notification.DEFAULT_SOUND; -// mThrottlingNotification.flags = Notification.FLAG_ONGOING_EVENT; } + mThrottlingNotification.flags = flags; mThrottlingNotification.tickerText = title; mThrottlingNotification.setLatestEventInfo(mContext, title, message, pi); @@ -415,9 +487,9 @@ public class ThrottleService extends IThrottleManager.Stub { private synchronized void clearThrottleAndNotification() { - if (mThrottleLevel == 1) { + if (mThrottleIndex != THROTTLE_INDEX_UNTHROTTLED) { synchronized (ThrottleService.this) { - mThrottleLevel = 0; + mThrottleIndex = THROTTLE_INDEX_UNTHROTTLED; } try { mNMService.setInterfaceThrottle(mPolicyIface, -1, -1); @@ -485,6 +557,7 @@ public class ThrottleService extends IThrottleManager.Stub { mRecorder.setNextPeriod(start,end); + mAlarmManager.cancel(mPendingResetIntent); mAlarmManager.set(AlarmManager.RTC_WAKEUP, end.getTimeInMillis(), mPendingResetIntent); } @@ -678,12 +751,6 @@ public class ThrottleService extends IThrottleManager.Stub { } long getPeriodRx(int which) { - if (DBG) { // TODO - remove - Slog.d(TAG, "reading slot "+ which +" with current =" + mCurrentPeriod); - for(int x = 0; x<mPeriodCount; x++) { - Slog.d(TAG, " " + x + " = " + mPeriodRxData[x]); - } - } synchronized (mParent) { if (which > mPeriodCount) return 0; which = mCurrentPeriod - which; diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index d6a42f6..3606629 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -25,6 +25,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.StatusBarManager; import android.app.UiModeManager; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -103,6 +104,14 @@ class UiModeManagerService extends IUiModeManager.Stub { private StatusBarManager mStatusBarManager; private final PowerManager.WakeLock mWakeLock; + static Intent buildHomeIntent(String category) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(category); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + return intent; + } + // The broadcast receiver which receives the result of the ordered broadcast sent when // the dock state changes. The original ordered broadcast is sent with an initial result // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g., @@ -114,24 +123,36 @@ class UiModeManagerService extends IUiModeManager.Stub { return; } + final int enableFlags = intent.getIntExtra("enableFlags", 0); + final int disableFlags = intent.getIntExtra("disableFlags", 0); + synchronized (mLock) { // Launch a dock activity - String category; + String category = null; if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { - // Only launch car home when car mode is enabled. - category = Intent.CATEGORY_CAR_DOCK; + // Only launch car home when car mode is enabled and the caller + // has asked us to switch to it. + if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { + category = Intent.CATEGORY_CAR_DOCK; + } } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(intent.getAction())) { - category = Intent.CATEGORY_DESK_DOCK; + // Only launch car home when desk mode is enabled and the caller + // has asked us to switch to it. Currently re-using the car + // mode flag since we don't have a formal API for "desk mode". + if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { + category = Intent.CATEGORY_DESK_DOCK; + } } else { - category = null; + // Launch the standard home app if requested. + if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { + category = Intent.CATEGORY_HOME; + } } + if (category != null) { // This is the new activity that will serve as home while // we are in care mode. - intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(category); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + Intent homeIntent = buildHomeIntent(category); // Now we are going to be careful about switching the // configuration and starting the activity -- we need to @@ -148,7 +169,7 @@ class UiModeManagerService extends IUiModeManager.Stub { } try { ActivityManagerNative.getDefault().startActivityWithConfig( - null, intent, null, null, 0, null, null, 0, false, false, + null, homeIntent, null, null, 0, null, null, 0, false, false, newConfig); mHoldingConfiguration = false; } catch (RemoteException e) { @@ -188,7 +209,7 @@ class UiModeManagerService extends IUiModeManager.Stub { mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0); synchronized (mLock) { if (mSystemReady) { - updateLocked(0); + updateLocked(0, 0); } } } @@ -302,16 +323,16 @@ class UiModeManagerService extends IUiModeManager.Stub { synchronized (mLock) { setCarModeLocked(false); if (mSystemReady) { - updateLocked(flags); + updateLocked(0, flags); } } } - public void enableCarMode() { + public void enableCarMode(int flags) { synchronized (mLock) { setCarModeLocked(true); if (mSystemReady) { - updateLocked(0); + updateLocked(flags, 0); } } } @@ -342,7 +363,7 @@ class UiModeManagerService extends IUiModeManager.Stub { Settings.Secure.UI_NIGHT_MODE, mode); Binder.restoreCallingIdentity(ident); mNightMode = mode; - updateLocked(0); + updateLocked(0, 0); } } } @@ -355,7 +376,7 @@ class UiModeManagerService extends IUiModeManager.Stub { synchronized (mLock) { mSystemReady = true; mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR; - updateLocked(0); + updateLocked(0, 0); mHandler.sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES); } } @@ -376,7 +397,7 @@ class UiModeManagerService extends IUiModeManager.Stub { mDockState = newState; setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR); if (mSystemReady) { - updateLocked(0); + updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0); } } } @@ -426,7 +447,7 @@ class UiModeManagerService extends IUiModeManager.Stub { } } - final void updateLocked(int flags) { + final void updateLocked(int enableFlags, int disableFlags) { long ident = Binder.clearCallingIdentity(); try { @@ -469,35 +490,40 @@ class UiModeManagerService extends IUiModeManager.Stub { // not launch the corresponding dock application. This gives apps a chance // to override the behavior and stay in their app even when the device is // placed into a dock. - mContext.sendOrderedBroadcast(new Intent(action), null, + Intent intent = new Intent(action); + intent.putExtra("enableFlags", enableFlags); + intent.putExtra("disableFlags", disableFlags); + mContext.sendOrderedBroadcast(intent, null, mResultReceiver, null, Activity.RESULT_OK, null, null); // Attempting to make this transition a little more clean, we are going // to hold off on doing a configuration change until we have finished - // the broacast and started the home activity. + // the broadcast and started the home activity. mHoldingConfiguration = true; - } - - if (oldAction != null && (flags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { - // We are exiting the special mode, and have been asked to return - // to the main home screen while doing so. To keep this clean, we - // have the activity manager switch the configuration for us at the - // same time as the switch. - try { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_HOME); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mHoldingConfiguration = false; - updateConfigurationLocked(false); - ActivityManagerNative.getDefault().startActivityWithConfig( - null, intent, null, null, 0, null, null, 0, false, false, - mConfiguration); - } catch (RemoteException e) { - Slog.w(TAG, e.getCause()); - } } else { - updateConfigurationLocked(true); + Intent homeIntent = null; + if (mCarModeEnabled) { + if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { + homeIntent = buildHomeIntent(Intent.CATEGORY_CAR_DOCK); + } + } else if (mDockState == Intent.EXTRA_DOCK_STATE_DESK) { + if ((enableFlags&UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) { + homeIntent = buildHomeIntent(Intent.CATEGORY_DESK_DOCK); + } + } else { + if ((disableFlags&UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) { + homeIntent = buildHomeIntent(Intent.CATEGORY_HOME); + } + } + if (homeIntent != null) { + try { + mContext.startActivity(homeIntent); + } catch (ActivityNotFoundException e) { + } + } } + updateConfigurationLocked(true); + // keep screen on when charging and in car mode boolean keepScreenOn = mCharging && ((mCarModeEnabled && mCarModeKeepsScreenOn) || @@ -569,7 +595,7 @@ class UiModeManagerService extends IUiModeManager.Stub { if (isDoingNightMode() && mLocation != null && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { updateTwilightLocked(); - updateLocked(0); + updateLocked(0, 0); } } break; @@ -597,7 +623,7 @@ class UiModeManagerService extends IUiModeManager.Stub { if (isDoingNightMode() && mLocation != null && mNightMode == UiModeManager.MODE_NIGHT_AUTO) { updateTwilightLocked(); - updateLocked(0); + updateLocked(0, 0); } } } diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java index 70fdadf..1f8bbcf 100644 --- a/telephony/java/com/android/internal/telephony/DataConnection.java +++ b/telephony/java/com/android/internal/telephony/DataConnection.java @@ -310,7 +310,8 @@ public abstract class DataConnection extends HierarchicalStateMachine { phone.mCM.deactivateDataCall(cid, obtainMessage(EVENT_DEACTIVATE_DONE, o)); } else { if (DBG) log("tearDownData radio is off sendMessage EVENT_DEACTIVATE_DONE immediately"); - sendMessage(obtainMessage(EVENT_DEACTIVATE_DONE, o)); + AsyncResult ar = new AsyncResult(o, null, null); + sendMessage(obtainMessage(EVENT_DEACTIVATE_DONE, ar)); } } diff --git a/tests/ImfTest/src/com/android/imftest/samples/OneEditTextActivitySelected.java b/tests/ImfTest/src/com/android/imftest/samples/OneEditTextActivitySelected.java index f313a90..64882aa 100644 --- a/tests/ImfTest/src/com/android/imftest/samples/OneEditTextActivitySelected.java +++ b/tests/ImfTest/src/com/android/imftest/samples/OneEditTextActivitySelected.java @@ -55,7 +55,11 @@ public class OneEditTextActivitySelected extends Activity ((ScrollView) mRootView).addView(layout); setContentView(mRootView); - } + + // set to resize so IME is always shown (and also so + // ImfBaseTestCase#destructiveCheckImeInitialState thinks it should always be shown + this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } public View getRootView() { return mRootView; diff --git a/tests/ImfTest/tests/src/com/android/imftest/samples/ImfBaseTestCase.java b/tests/ImfTest/tests/src/com/android/imftest/samples/ImfBaseTestCase.java index 1957640..50e2009 100755 --- a/tests/ImfTest/tests/src/com/android/imftest/samples/ImfBaseTestCase.java +++ b/tests/ImfTest/tests/src/com/android/imftest/samples/ImfBaseTestCase.java @@ -19,6 +19,7 @@ package com.android.imftest.samples; import android.app.Activity; import android.app.KeyguardManager; import android.content.Context; +import android.content.res.Configuration; import android.os.SystemClock; import android.test.InstrumentationTestCase; import android.view.KeyEvent; @@ -43,7 +44,6 @@ public abstract class ImfBaseTestCase<T extends Activity> extends Instrumentatio public final int IME_MIN_HEIGHT = 150; public final int IME_MAX_HEIGHT = 300; - public final String TARGET_PACKAGE_NAME = "com.android.imftest"; protected InputMethodManager mImm; protected T mTargetActivity; protected boolean mExpectAutoPop; @@ -56,9 +56,12 @@ public abstract class ImfBaseTestCase<T extends Activity> extends Instrumentatio @Override public void setUp() throws Exception { super.setUp(); + final String packageName = getInstrumentation().getTargetContext().getPackageName(); + mTargetActivity = launchActivity(packageName, mTargetActivityClass, null); + // expect ime to auto pop up if device has no hard keyboard + mExpectAutoPop = mTargetActivity.getResources().getConfiguration().hardKeyboardHidden == + Configuration.HARDKEYBOARDHIDDEN_YES; - mTargetActivity = launchActivity(TARGET_PACKAGE_NAME, mTargetActivityClass, null); - mExpectAutoPop = mTargetActivity.getResources().getBoolean(R.bool.def_expect_ime_autopop); mImm = InputMethodManager.getInstance(mTargetActivity); KeyguardManager keyguardManager = @@ -104,9 +107,9 @@ public abstract class ImfBaseTestCase<T extends Activity> extends Instrumentatio } public void destructiveCheckImeInitialState(View rootView, View servedView) { - if (mExpectAutoPop && (mTargetActivity.getWindow().getAttributes(). - softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) - == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) { + int windowSoftInputMode = mTargetActivity.getWindow().getAttributes().softInputMode; + int adjustMode = windowSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; + if (mExpectAutoPop && adjustMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) { assertTrue(destructiveCheckImeUp(rootView, servedView)); } else { assertFalse(destructiveCheckImeUp(rootView, servedView)); diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java index e4f9794..619ab30 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java @@ -208,6 +208,12 @@ public class Paint extends _Original_Paint { this(0); } + /* + * Do not remove or com.android.layoutlib.bridge.TestClassReplacement fails. + */ + @Override + public void finalize() { } + public Paint(int flags) { setFlags(flags | DEFAULT_PAINT_FLAGS); initFont(); |