diff options
60 files changed, 2145 insertions, 1341 deletions
diff --git a/Android.mk b/Android.mk index 682f2865d1ad..ab1e7ea0ea12 100644 --- a/Android.mk +++ b/Android.mk @@ -222,6 +222,7 @@ aidl_files := \ frameworks/base/core/java/android/content/ComponentName.aidl \ frameworks/base/core/java/android/content/Intent.aidl \ frameworks/base/core/java/android/content/IntentSender.aidl \ + frameworks/base/core/java/android/content/PeriodicSync.aidl \ frameworks/base/core/java/android/content/SyncStats.aidl \ frameworks/base/core/java/android/content/res/Configuration.aidl \ frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \ diff --git a/api/current.xml b/api/current.xml index 66880e1108f6..c54fc98d2e27 100644 --- a/api/current.xml +++ b/api/current.xml @@ -1035,6 +1035,17 @@ visibility="public" > </field> +<field name="SET_TIME" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.permission.SET_TIME"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="SET_TIME_ZONE" type="java.lang.String" transient="false" @@ -18808,6 +18819,19 @@ <parameter name="operation" type="android.app.PendingIntent"> </parameter> </method> +<method name="setTime" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="millis" type="long"> +</parameter> +</method> <method name="setTimeZone" return="void" abstract="false" @@ -31083,6 +31107,25 @@ <parameter name="name" type="java.lang.String"> </parameter> </method> +<method name="addPeriodicSync" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="account" type="android.accounts.Account"> +</parameter> +<parameter name="authority" type="java.lang.String"> +</parameter> +<parameter name="extras" type="android.os.Bundle"> +</parameter> +<parameter name="pollFrequency" type="long"> +</parameter> +</method> <method name="addStatusChangeListener" return="java.lang.Object" abstract="false" @@ -31203,6 +31246,21 @@ visibility="public" > </method> +<method name="getPeriodicSyncs" + return="java.util.List<android.content.PeriodicSync>" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="account" type="android.accounts.Account"> +</parameter> +<parameter name="authority" type="java.lang.String"> +</parameter> +</method> <method name="getSyncAdapterTypes" return="android.content.SyncAdapterType[]" abstract="false" @@ -31438,6 +31496,23 @@ <parameter name="observer" type="android.database.ContentObserver"> </parameter> </method> +<method name="removePeriodicSync" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="account" type="android.accounts.Account"> +</parameter> +<parameter name="authority" type="java.lang.String"> +</parameter> +<parameter name="extras" type="android.os.Bundle"> +</parameter> +</method> <method name="removeStatusChangeListener" return="void" abstract="false" @@ -39721,6 +39796,109 @@ > </method> </class> +<class name="PeriodicSync" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.os.Parcelable"> +</implements> +<constructor name="PeriodicSync" + type="android.content.PeriodicSync" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="account" type="android.accounts.Account"> +</parameter> +<parameter name="authority" type="java.lang.String"> +</parameter> +<parameter name="extras" type="android.os.Bundle"> +</parameter> +<parameter name="period" type="long"> +</parameter> +</constructor> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="writeToParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dest" type="android.os.Parcel"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="account" + type="android.accounts.Account" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="authority" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="extras" + type="android.os.Bundle" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="period" + type="long" + transient="false" + volatile="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="ReceiverCallNotAllowedException" extends="android.util.AndroidRuntimeException" abstract="false" @@ -52892,40 +53070,40 @@ <parameter name="sleepAfterYieldDelay" type="long"> </parameter> </method> -<field name="CREATE_IF_NECESSARY" +<field name="CONFLICT_ABORT" type="int" transient="false" volatile="false" - value="268435456" + value="2" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="NO_LOCALIZED_COLLATORS" +<field name="CONFLICT_FAIL" type="int" transient="false" volatile="false" - value="16" + value="3" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="OPEN_READONLY" +<field name="CONFLICT_IGNORE" type="int" transient="false" volatile="false" - value="1" + value="4" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="OPEN_READWRITE" +<field name="CONFLICT_NONE" type="int" transient="false" volatile="false" @@ -52936,86 +53114,77 @@ visibility="public" > </field> -<field name="SQLITE_MAX_LIKE_PATTERN_LENGTH" +<field name="CONFLICT_REPLACE" type="int" transient="false" volatile="false" - value="50000" + value="5" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -</class> -<class name="SQLiteDatabase.ConflictAlgorithm" - extends="java.lang.Object" - abstract="false" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -<field name="ABORT" +<field name="CONFLICT_ROLLBACK" type="int" transient="false" volatile="false" - value="2" + value="1" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="FAIL" +<field name="CREATE_IF_NECESSARY" type="int" transient="false" volatile="false" - value="3" + value="268435456" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="IGNORE" +<field name="NO_LOCALIZED_COLLATORS" type="int" transient="false" volatile="false" - value="4" + value="16" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="NONE" +<field name="OPEN_READONLY" type="int" transient="false" volatile="false" - value="0" + value="1" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="REPLACE" +<field name="OPEN_READWRITE" type="int" transient="false" volatile="false" - value="5" + value="0" static="true" final="true" deprecated="not deprecated" visibility="public" > </field> -<field name="ROLLBACK" +<field name="SQLITE_MAX_LIKE_PATTERN_LENGTH" type="int" transient="false" volatile="false" - value="1" + value="50000" static="true" final="true" deprecated="not deprecated" @@ -192449,6 +192618,49 @@ <parameter name="mode" type="int"> </parameter> </method> +<method name="smoothScrollBy" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="distance" type="int"> +</parameter> +<parameter name="duration" type="int"> +</parameter> +</method> +<method name="smoothScrollToPosition" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="position" type="int"> +</parameter> +</method> +<method name="smoothScrollToPosition" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="position" type="int"> +</parameter> +<parameter name="boundPosition" type="int"> +</parameter> +</method> <method name="verifyDrawable" return="boolean" abstract="false" @@ -200701,223 +200913,6 @@ </parameter> </method> </interface> -<class name="NumberPicker" - extends="android.widget.LinearLayout" - abstract="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<constructor name="NumberPicker" - type="android.widget.NumberPicker" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="context" type="android.content.Context"> -</parameter> -</constructor> -<constructor name="NumberPicker" - type="android.widget.NumberPicker" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="context" type="android.content.Context"> -</parameter> -<parameter name="attrs" type="android.util.AttributeSet"> -</parameter> -</constructor> -<method name="changeCurrent" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="protected" -> -<parameter name="current" type="int"> -</parameter> -</method> -<method name="getBeginRange" - return="int" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="protected" -> -</method> -<method name="getCurrent" - return="int" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> -<method name="getEndRange" - return="int" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="protected" -> -</method> -<method name="setCurrent" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="current" type="int"> -</parameter> -</method> -<method name="setFormatter" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="formatter" type="android.widget.NumberPicker.Formatter"> -</parameter> -</method> -<method name="setOnChangeListener" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="listener" type="android.widget.NumberPicker.OnChangedListener"> -</parameter> -</method> -<method name="setRange" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="start" type="int"> -</parameter> -<parameter name="end" type="int"> -</parameter> -</method> -<method name="setRange" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="start" type="int"> -</parameter> -<parameter name="end" type="int"> -</parameter> -<parameter name="displayedValues" type="java.lang.String[]"> -</parameter> -</method> -<method name="setSpeed" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="speed" type="long"> -</parameter> -</method> -<field name="TWO_DIGIT_FORMATTER" - type="android.widget.NumberPicker.Formatter" - transient="false" - volatile="false" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> -</class> -<interface name="NumberPicker.Formatter" - abstract="true" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -<method name="toString" - return="java.lang.String" - abstract="true" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="value" type="int"> -</parameter> -</method> -</interface> -<interface name="NumberPicker.OnChangedListener" - abstract="true" - static="true" - final="false" - deprecated="not deprecated" - visibility="public" -> -<method name="onChanged" - return="void" - abstract="true" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="picker" type="android.widget.NumberPicker"> -</parameter> -<parameter name="oldVal" type="int"> -</parameter> -<parameter name="newVal" type="int"> -</parameter> -</method> -</interface> <class name="PopupWindow" extends="java.lang.Object" abstract="false" diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp index 2ec0b702d41f..845c85471f92 100644 --- a/cmds/stagefright/record.cpp +++ b/cmds/stagefright/record.cpp @@ -106,6 +106,9 @@ sp<MediaSource> createSource(const char *filename) { sp<MediaExtractor> extractor = MediaExtractor::Create(new FileSource(filename)); + if (extractor == NULL) { + return NULL; + } size_t num_tracks = extractor->countTracks(); diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index e65cdf11545b..f7cb22730480 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -431,6 +431,10 @@ int main(int argc, char **argv) { mediaSource = new JPEGSource(dataSource); } else { sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); + if (extractor == NULL) { + fprintf(stderr, "could not create data source\n"); + return -1; + } size_t numTracks = extractor->countTracks(); diff --git a/common/java/com/android/common/Base64.java b/common/java/com/android/common/Base64.java index 0c8e7c103822..771875c038b6 100644 --- a/common/java/com/android/common/Base64.java +++ b/common/java/com/android/common/Base64.java @@ -22,6 +22,11 @@ package com.android.common; */ public class Base64 { /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + + /** * Encoder flag bit to indicate you want the padding '=' * characters at the end (if any) to be omitted. */ @@ -106,7 +111,7 @@ public class Base64 { * @param input the input String to decode, which is converted to * bytes using the default charset * @param flags controls certain features of the decoded output. - * Passing 0 to decode standard Base64. + * Pass {@code DEFAULT} to decode standard Base64. * * @throws IllegalArgumentException if the input contains * incorrect padding @@ -124,7 +129,7 @@ public class Base64 { * * @param input the input array to decode * @param flags controls certain features of the decoded output. - * Passing 0 to decode standard Base64. + * Pass {@code DEFAULT} to decode standard Base64. * * @throws IllegalArgumentException if the input contains * incorrect padding @@ -144,7 +149,7 @@ public class Base64 { * @param offset the position within the input array at which to start * @param len the number of bytes of input to decode * @param flags controls certain features of the decoded output. - * Passing 0 to decode standard Base64. + * Pass {@code DEFAULT} to decode standard Base64. * * @throws IllegalArgumentException if the input contains * incorrect padding @@ -362,8 +367,8 @@ public class Base64 { * * @param input the data to encode * @param flags controls certain features of the encoded output. - * Passing 0 results in output that adheres to RFC - * 2045. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. */ public static String encodeToString(byte[] input, int flags) { return new String(encode(input, flags)); @@ -378,8 +383,8 @@ public class Base64 { * start * @param len the number of bytes of input to encode * @param flags controls certain features of the encoded output. - * Passing 0 results in output that adheres to RFC - * 2045. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. */ public static String encodeToString(byte[] input, int offset, int len, int flags) { return new String(encode(input, offset, len, flags)); @@ -391,8 +396,8 @@ public class Base64 { * * @param input the data to encode * @param flags controls certain features of the encoded output. - * Passing 0 results in output that adheres to RFC - * 2045. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. */ public static byte[] encode(byte[] input, int flags) { return encode(input, 0, input.length, flags); @@ -407,8 +412,8 @@ public class Base64 { * start * @param len the number of bytes of input to encode * @param flags controls certain features of the encoded output. - * Passing 0 results in output that adheres to RFC - * 2045. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. */ public static byte[] encode(byte[] input, int offset, int len, int flags) { final boolean do_padding = (flags & NO_PADDING) == 0; @@ -494,4 +499,6 @@ public class Base64 { assert op == output.length; return output; } + + private Base64() { } // don't instantiate } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 414d9633b11e..19e741ab55ff 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -271,7 +271,7 @@ public class AccountManager { } /** - * Add an account to the AccountManager's set of known accounts. + * Add an account to the AccountManager's set of known accounts. * <p> * Requires that the caller has permission * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running @@ -560,9 +560,13 @@ public class AccountManager { * user to enter credentials. If it is able to retrieve the authtoken it will be returned * in the result. * <p> - * If the authenticator needs to prompt the user for credentials it will return an intent for + * If the authenticator needs to prompt the user for credentials, rather than returning the + * authtoken it will instead return an intent for * an activity that will do the prompting. If an intent is returned and notifyAuthFailure - * is true then a notification will be created that launches this intent. + * is true then a notification will be created that launches this intent. This intent can be + * invoked by the caller directly to start the activity that prompts the user for the + * updated credentials. Otherwise this activity will not be run until the user activates + * the notification. * <p> * This call returns immediately but runs asynchronously and the result is accessed via the * {@link AccountManagerFuture} that is returned. This future is also passed as the sole @@ -653,7 +657,7 @@ public class AccountManager { if (accountType == null) { Log.e(TAG, "the account must not be null"); // to unblock caller waiting on Future.get() - set(new Bundle()); + set(new Bundle()); return; } mService.addAcount(mResponse, accountType, authTokenType, @@ -1372,7 +1376,7 @@ public class AccountManager { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION); // To recover from disk-full. - intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); + intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); } } diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 53c79352d3f4..90820036d40b 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -277,7 +277,26 @@ public class AlarmManager } catch (RemoteException ex) { } } - + + /** + * Set the system wall clock time. + * Requires the permission android.permission.SET_TIME. + * + * @param millis time in milliseconds since the Epoch + */ + public void setTime(long millis) { + try { + mService.setTime(millis); + } catch (RemoteException ex) { + } + } + + /** + * Set the system default time zone. + * Requires the permission android.permission.SET_TIME_ZONE. + * + * @param timeZone in the format understood by {@link java.util.TimeZone} + */ public void setTimeZone(String timeZone) { try { mService.setTimeZone(timeZone); diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl index cb42236f413c..edb40ed77f51 100755 --- a/core/java/android/app/IAlarmManager.aidl +++ b/core/java/android/app/IAlarmManager.aidl @@ -27,6 +27,7 @@ interface IAlarmManager { void set(int type, long triggerAtTime, in PendingIntent operation); void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation); void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation); + void setTime(long millis); void setTimeZone(String zone); void remove(in PendingIntent operation); } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index eb2d7b143dc7..b5587eda7fd6 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -42,6 +42,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.ArrayList; +import java.util.Collection; /** @@ -966,6 +967,65 @@ public abstract class ContentResolver { } /** + * Specifies that a sync should be requested with the specified the account, authority, + * and extras at the given frequency. If there is already another periodic sync scheduled + * with the account, authority and extras then a new periodic sync won't be added, instead + * the frequency of the previous one will be updated. + * <p> + * These periodic syncs honor the "syncAutomatically" and "masterSyncAutomatically" settings. + * Although these sync are scheduled at the specified frequency, it may take longer for it to + * actually be started if other syncs are ahead of it in the sync operation queue. This means + * that the actual start time may drift. + * + * @param account the account to specify in the sync + * @param authority the provider to specify in the sync request + * @param extras extra parameters to go along with the sync request + * @param pollFrequency how frequently the sync should be performed, in seconds. + */ + public static void addPeriodicSync(Account account, String authority, Bundle extras, + long pollFrequency) { + validateSyncExtrasBundle(extras); + try { + getContentService().addPeriodicSync(account, authority, extras, pollFrequency); + } catch (RemoteException e) { + // exception ignored; if this is thrown then it means the runtime is in the midst of + // being restarted + } + } + + /** + * Remove a periodic sync. Has no affect if account, authority and extras don't match + * an existing periodic sync. + * + * @param account the account of the periodic sync to remove + * @param authority the provider of the periodic sync to remove + * @param extras the extras of the periodic sync to remove + */ + public static void removePeriodicSync(Account account, String authority, Bundle extras) { + validateSyncExtrasBundle(extras); + try { + getContentService().removePeriodicSync(account, authority, extras); + } catch (RemoteException e) { + throw new RuntimeException("the ContentService should always be reachable", e); + } + } + + /** + * Get the list of information about the periodic syncs for the given account and authority. + * + * @param account the account whose periodic syncs we are querying + * @param authority the provider whose periodic syncs we are querying + * @return a list of PeriodicSync objects. This list may be empty but will never be null. + */ + public static List<PeriodicSync> getPeriodicSyncs(Account account, String authority) { + try { + return getContentService().getPeriodicSyncs(account, authority); + } catch (RemoteException e) { + throw new RuntimeException("the ContentService should always be reachable", e); + } + } + + /** * Check if this account/provider is syncable. * @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet. */ diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index 974a6670aab4..e0dfab59ee22 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -32,6 +32,8 @@ import android.Manifest; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; +import java.util.List; /** * {@hide} @@ -273,6 +275,42 @@ public final class ContentService extends IContentService.Stub { } } + public void addPeriodicSync(Account account, String authority, Bundle extras, + long pollFrequency) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + long identityToken = clearCallingIdentity(); + try { + getSyncManager().getSyncStorageEngine().addPeriodicSync( + account, authority, extras, pollFrequency); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public void removePeriodicSync(Account account, String authority, Bundle extras) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + long identityToken = clearCallingIdentity(); + try { + getSyncManager().getSyncStorageEngine().removePeriodicSync(account, authority, extras); + } finally { + restoreCallingIdentity(identityToken); + } + } + + public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) { + mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, + "no permission to read the sync settings"); + long identityToken = clearCallingIdentity(); + try { + return getSyncManager().getSyncStorageEngine().getPeriodicSyncs( + account, providerName); + } finally { + restoreCallingIdentity(identityToken); + } + } + public int getIsSyncable(Account account, String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl index b0f14c15cbb4..2d906ed0d725 100644 --- a/core/java/android/content/IContentService.aidl +++ b/core/java/android/content/IContentService.aidl @@ -21,6 +21,7 @@ import android.content.ActiveSyncInfo; import android.content.ISyncStatusObserver; import android.content.SyncAdapterType; import android.content.SyncStatusInfo; +import android.content.PeriodicSync; import android.net.Uri; import android.os.Bundle; import android.database.IContentObserver; @@ -38,11 +39,11 @@ interface IContentService { void requestSync(in Account account, String authority, in Bundle extras); void cancelSync(in Account account, String authority); - + /** * Check if the provider should be synced when a network tickle is received * @param providerName the provider whose setting we are querying - * @return true of the provider should be synced when a network tickle is received + * @return true if the provider should be synced when a network tickle is received */ boolean getSyncAutomatically(in Account account, String providerName); @@ -55,6 +56,33 @@ interface IContentService { void setSyncAutomatically(in Account account, String providerName, boolean sync); /** + * Get the frequency of the periodic poll, if any. + * @param providerName the provider whose setting we are querying + * @return the frequency of the periodic sync in seconds. If 0 then no periodic syncs + * will take place. + */ + List<PeriodicSync> getPeriodicSyncs(in Account account, String providerName); + + /** + * Set whether or not the provider is to be synced on a periodic basis. + * + * @param providerName the provider whose behavior is being controlled + * @param pollFrequency the period that a sync should be performed, in seconds. If this is + * zero or less then no periodic syncs will be performed. + */ + void addPeriodicSync(in Account account, String providerName, in Bundle extras, + long pollFrequency); + + /** + * Set whether or not the provider is to be synced on a periodic basis. + * + * @param providerName the provider whose behavior is being controlled + * @param pollFrequency the period that a sync should be performed, in seconds. If this is + * zero or less then no periodic syncs will be performed. + */ + void removePeriodicSync(in Account account, String providerName, in Bundle extras); + + /** * Check if this account/provider is syncable. * @return >0 if it is syncable, 0 if not, and <0 if the state isn't known yet. */ @@ -69,15 +97,15 @@ interface IContentService { void setMasterSyncAutomatically(boolean flag); boolean getMasterSyncAutomatically(); - + /** * Returns true if there is currently a sync operation for the given * account or authority in the pending list, or actively being processed. */ boolean isSyncActive(in Account account, String authority); - + ActiveSyncInfo getActiveSync(); - + /** * Returns the types of the SyncAdapters that are registered with the system. * @return Returns the types of the SyncAdapters that are registered with the system. @@ -96,8 +124,8 @@ interface IContentService { * Return true if the pending status is true of any matching authorities. */ boolean isSyncPending(in Account account, String authority); - + void addStatusChangeListener(int mask, ISyncStatusObserver callback); - + void removeStatusChangeListener(ISyncStatusObserver callback); } diff --git a/core/java/android/os/Base64Utils.java b/core/java/android/content/PeriodicSync.aidl index 684a469703ef..4530591bb551 100644 --- a/core/java/android/os/Base64Utils.java +++ b/core/java/android/content/PeriodicSync.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006 The Android Open Source Project + * 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. @@ -14,18 +14,6 @@ * limitations under the License. */ -package android.os; - -/** - * {@hide} - */ -public class Base64Utils -{ - // TODO add encode api here if possible - - public static byte [] decodeBase64(String data) { - return decodeBase64Native(data); - } - private static native byte[] decodeBase64Native(String data); -} +package android.content; +parcelable PeriodicSync; diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java new file mode 100644 index 000000000000..17813ec3aaf0 --- /dev/null +++ b/core/java/android/content/PeriodicSync.java @@ -0,0 +1,84 @@ +/* + * 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.content; + +import android.os.Parcelable; +import android.os.Bundle; +import android.os.Parcel; +import android.accounts.Account; + +/** + * Value type that contains information about a periodic sync. Is parcelable, making it suitable + * for passing in an IPC. + */ +public class PeriodicSync implements Parcelable { + /** The account to be synced */ + public final Account account; + /** The authority of the sync */ + public final String authority; + /** Any extras that parameters that are to be passed to the sync adapter. */ + public final Bundle extras; + /** How frequently the sync should be scheduled, in seconds. */ + public final long period; + + /** Creates a new PeriodicSync, copying the Bundle */ + public PeriodicSync(Account account, String authority, Bundle extras, long period) { + this.account = account; + this.authority = authority; + this.extras = new Bundle(extras); + this.period = period; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + account.writeToParcel(dest, flags); + dest.writeString(authority); + dest.writeBundle(extras); + dest.writeLong(period); + } + + public static final Creator<PeriodicSync> CREATOR = new Creator<PeriodicSync>() { + public PeriodicSync createFromParcel(Parcel source) { + return new PeriodicSync(Account.CREATOR.createFromParcel(source), + source.readString(), source.readBundle(), source.readLong()); + } + + public PeriodicSync[] newArray(int size) { + return new PeriodicSync[size]; + } + }; + + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof PeriodicSync)) { + return false; + } + + final PeriodicSync other = (PeriodicSync) o; + + return account.equals(other.account) + && authority.equals(other.authority) + && period == other.period + && SyncStorageEngine.equals(extras, other.extras); + } +} diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 699b61d2b5d4..619c7d5e2067 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -52,14 +52,7 @@ import android.util.EventLog; import android.util.Log; import android.util.Pair; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; @@ -74,12 +67,6 @@ import java.util.concurrent.CountDownLatch; public class SyncManager implements OnAccountsUpdateListener { private static final String TAG = "SyncManager"; - // used during dumping of the Sync history - private static final long MILLIS_IN_HOUR = 1000 * 60 * 60; - private static final long MILLIS_IN_DAY = MILLIS_IN_HOUR * 24; - private static final long MILLIS_IN_WEEK = MILLIS_IN_DAY * 7; - private static final long MILLIS_IN_4WEEKS = MILLIS_IN_WEEK * 4; - /** Delay a sync due to local changes this long. In milliseconds */ private static final long LOCAL_SYNC_DELAY; @@ -157,9 +144,7 @@ public class SyncManager implements OnAccountsUpdateListener { // set if the sync active indicator should be reported private boolean mNeedSyncActiveNotification = false; - private volatile boolean mSyncPollInitialized; private final PendingIntent mSyncAlarmIntent; - private final PendingIntent mSyncPollAlarmIntent; // Synchronized on "this". Instead of using this directly one should instead call // its accessor, getConnManager(). private ConnectivityManager mConnManagerDoNotUseDirectly; @@ -276,7 +261,6 @@ public class SyncManager implements OnAccountsUpdateListener { // ignore the rest of the states -- leave our boolean alone. } if (mDataConnectionIsConnected) { - initializeSyncPoll(); sendCheckAlarmsMessage(); } } @@ -291,14 +275,8 @@ public class SyncManager implements OnAccountsUpdateListener { }; private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; - private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM"; private final SyncHandler mSyncHandler; - private static final int MAX_SYNC_POLL_DELAY_SECONDS = 36 * 60 * 60; // 36 hours - private static final int MIN_SYNC_POLL_DELAY_SECONDS = 24 * 60 * 60; // 24 hours - - private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs"; - private volatile boolean mBootCompleted = false; private ConnectivityManager getConnectivityManager() { @@ -338,9 +316,6 @@ public class SyncManager implements OnAccountsUpdateListener { mSyncAlarmIntent = PendingIntent.getBroadcast( mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0); - mSyncPollAlarmIntent = PendingIntent.getBroadcast( - mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0); - IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); context.registerReceiver(mConnectivityIntentReceiver, intentFilter); @@ -396,49 +371,6 @@ public class SyncManager implements OnAccountsUpdateListener { } } - private synchronized void initializeSyncPoll() { - if (mSyncPollInitialized) return; - mSyncPollInitialized = true; - - mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM)); - - // load the next poll time from shared preferences - long absoluteAlarmTime = readSyncPollTime(); - - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime); - } - - // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then - // schedule the poll immediately, if it is too far in the future then cap it at - // MAX_SYNC_POLL_DELAY_SECONDS. - long absoluteNow = System.currentTimeMillis(); - long relativeNow = SystemClock.elapsedRealtime(); - long relativeAlarmTime = relativeNow; - if (absoluteAlarmTime > absoluteNow) { - long delayInMs = absoluteAlarmTime - absoluteNow; - final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000; - if (delayInMs > maxDelayInMs) { - delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000; - } - relativeAlarmTime += delayInMs; - } - - // schedule an alarm for the next poll time - scheduleSyncPollAlarm(relativeAlarmTime); - } - - private void scheduleSyncPollAlarm(long relativeAlarmTime) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime - + ", now is " + SystemClock.elapsedRealtime() - + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime())); - } - ensureAlarmService(); - mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime, - mSyncPollAlarmIntent); - } - /** * Return a random value v that satisfies minValue <= v < maxValue. The difference between * maxValue and minValue must be less than Integer.MAX_VALUE. @@ -453,68 +385,6 @@ public class SyncManager implements OnAccountsUpdateListener { return minValue + random.nextInt((int)spread); } - private void handleSyncPollAlarm() { - // determine the next poll time - long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000; - long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs; - - if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs); - - // write the absolute time to shared preferences - writeSyncPollTime(System.currentTimeMillis() + delayMs); - - // schedule an alarm for the next poll time - scheduleSyncPollAlarm(nextRelativePollTimeMs); - - // perform a poll - scheduleSync(null /* sync all syncable accounts */, null /* sync all syncable providers */, - new Bundle(), 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); - } - - private void writeSyncPollTime(long when) { - File f = new File(SYNCMANAGER_PREFS_FILENAME); - DataOutputStream str = null; - try { - str = new DataOutputStream(new FileOutputStream(f)); - str.writeLong(when); - } catch (FileNotFoundException e) { - Log.w(TAG, "error writing to file " + f, e); - } catch (IOException e) { - Log.w(TAG, "error writing to file " + f, e); - } finally { - if (str != null) { - try { - str.close(); - } catch (IOException e) { - Log.w(TAG, "error closing file " + f, e); - } - } - } - } - - private long readSyncPollTime() { - File f = new File(SYNCMANAGER_PREFS_FILENAME); - - DataInputStream str = null; - try { - str = new DataInputStream(new FileInputStream(f)); - return str.readLong(); - } catch (FileNotFoundException e) { - writeSyncPollTime(0); - } catch (IOException e) { - Log.w(TAG, "error reading file " + f, e); - } finally { - if (str != null) { - try { - str.close(); - } catch (IOException e) { - Log.w(TAG, "error closing file " + f, e); - } - } - } - return 0; - } - public ActiveSyncContext getActiveSyncContext() { return mActiveSyncContext; } @@ -799,12 +669,6 @@ public class SyncManager implements OnAccountsUpdateListener { } } - class SyncPollAlarmReceiver extends BroadcastReceiver { - public void onReceive(Context context, Intent intent) { - handleSyncPollAlarm(); - } - } - private void clearBackoffSetting(SyncOperation op) { mSyncStorageEngine.setBackoff(op.account, op.authority, SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); @@ -923,7 +787,7 @@ public class SyncManager implements OnAccountsUpdateListener { mSyncStorageEngine.setBackoff(account, authority, SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE); synchronized (mSyncQueue) { - mSyncQueue.clear(account, authority); + mSyncQueue.remove(account, authority); } } @@ -1084,7 +948,8 @@ public class SyncManager implements OnAccountsUpdateListener { pw.println("none"); } final long now = SystemClock.elapsedRealtime(); - pw.print("now: "); pw.println(now); + pw.print("now: "); pw.print(now); + pw.println(" (" + formatTime(System.currentTimeMillis()) + ")"); pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000)); pw.println(" (HH:MM:SS)"); pw.print("time spent syncing: "); @@ -1102,7 +967,9 @@ public class SyncManager implements OnAccountsUpdateListener { pw.println("no alarm is scheduled (there had better not be any pending syncs)"); } - pw.print("active sync: "); pw.println(mActiveSyncContext); + final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext; + + pw.print("active sync: "); pw.println(activeSyncContext); pw.print("notification info: "); sb.setLength(0); @@ -1125,6 +992,11 @@ public class SyncManager implements OnAccountsUpdateListener { pw.print(authority != null ? authority.account : "<no account>"); pw.print(" "); pw.print(authority != null ? authority.authority : "<no account>"); + if (activeSyncContext != null) { + pw.print(" "); + pw.print(SyncStorageEngine.SOURCES[ + activeSyncContext.mSyncOperation.syncSource]); + } pw.print(", duration is "); pw.println(DateUtils.formatElapsedTime(durationInSeconds)); } else { @@ -1152,80 +1024,76 @@ public class SyncManager implements OnAccountsUpdateListener { } } - HashSet<Account> processedAccounts = new HashSet<Account>(); - ArrayList<SyncStatusInfo> statuses - = mSyncStorageEngine.getSyncStatus(); - if (statuses != null && statuses.size() > 0) { - pw.println(); - pw.println("Sync Status"); - final int N = statuses.size(); - for (int i=0; i<N; i++) { - SyncStatusInfo status = statuses.get(i); - SyncStorageEngine.AuthorityInfo authority - = mSyncStorageEngine.getAuthority(status.authorityId); - if (authority != null) { - Account curAccount = authority.account; - - if (processedAccounts.contains(curAccount)) { - continue; - } - - processedAccounts.add(curAccount); + // join the installed sync adapter with the accounts list and emit for everything + pw.println(); + pw.println("Sync Status"); + for (Account account : accounts) { + pw.print(" Account "); pw.print(account.name); + pw.print(" "); pw.print(account.type); + pw.println(":"); + for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : + mSyncAdapters.getAllServices()) { + if (!syncAdapterType.type.accountType.equals(account.type)) { + continue; + } - pw.print(" Account "); pw.print(authority.account.name); - pw.print(" "); pw.print(authority.account.type); - pw.println(":"); - for (int j=i; j<N; j++) { - status = statuses.get(j); - authority = mSyncStorageEngine.getAuthority(status.authorityId); - if (!curAccount.equals(authority.account)) { - continue; - } - pw.print(" "); pw.print(authority.authority); - pw.println(":"); - final String syncable = authority.syncable > 0 - ? "syncable" - : (authority.syncable == 0 ? "not syncable" : "not initialized"); - final String enabled = authority.enabled ? "enabled" : "disabled"; - final String delayUntil = authority.delayUntil > now - ? "delay for " + ((authority.delayUntil - now) / 1000) + " sec" - : "no delay required"; - final String backoff = authority.backoffTime > now - ? "backoff for " + ((authority.backoffTime - now) / 1000) - + " sec" - : "no backoff required"; - final String backoffDelay = authority.backoffDelay > 0 - ? ("the backoff increment is " + authority.backoffDelay / 1000 - + " sec") - : "no backoff increment"; - pw.println(String.format( - " settings: %s, %s, %s, %s, %s", - enabled, syncable, backoff, backoffDelay, delayUntil)); - pw.print(" count: local="); pw.print(status.numSourceLocal); - pw.print(" poll="); pw.print(status.numSourcePoll); - pw.print(" server="); pw.print(status.numSourceServer); - pw.print(" user="); pw.print(status.numSourceUser); - pw.print(" total="); pw.println(status.numSyncs); - pw.print(" total duration: "); - pw.println(DateUtils.formatElapsedTime( - status.totalElapsedTime/1000)); - if (status.lastSuccessTime != 0) { - pw.print(" SUCCESS: source="); - pw.print(SyncStorageEngine.SOURCES[ - status.lastSuccessSource]); - pw.print(" time="); - pw.println(formatTime(status.lastSuccessTime)); - } else { - pw.print(" FAILURE: source="); - pw.print(SyncStorageEngine.SOURCES[ - status.lastFailureSource]); - pw.print(" initialTime="); - pw.print(formatTime(status.initialFailureTime)); - pw.print(" lastTime="); - pw.println(formatTime(status.lastFailureTime)); - pw.print(" message: "); pw.println(status.lastFailureMesg); - } - } + SyncStorageEngine.AuthorityInfo settings = mSyncStorageEngine.getAuthority( + account, syncAdapterType.type.authority); + SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings); + pw.print(" "); pw.print(settings.authority); + pw.println(":"); + pw.print(" settings:"); + pw.print(" " + (settings.syncable > 0 + ? "syncable" + : (settings.syncable == 0 ? "not syncable" : "not initialized"))); + pw.print(", " + (settings.enabled ? "enabled" : "disabled")); + if (settings.delayUntil > now) { + pw.print(", delay for " + + ((settings.delayUntil - now) / 1000) + " sec"); + } + if (settings.backoffTime > now) { + pw.print(", backoff for " + + ((settings.backoffTime - now) / 1000) + " sec"); + } + if (settings.backoffDelay > 0) { + pw.print(", the backoff increment is " + settings.backoffDelay / 1000 + + " sec"); + } + pw.println(); + for (int periodicIndex = 0; + periodicIndex < settings.periodicSyncs.size(); + periodicIndex++) { + Pair<Bundle, Long> info = settings.periodicSyncs.get(periodicIndex); + long lastPeriodicTime = status.getPeriodicSyncTime(periodicIndex); + long nextPeriodicTime = lastPeriodicTime + info.second * 1000; + pw.println(" periodic period=" + info.second + + ", extras=" + info.first + + ", next=" + formatTime(nextPeriodicTime)); + } + pw.print(" count: local="); pw.print(status.numSourceLocal); + pw.print(" poll="); pw.print(status.numSourcePoll); + pw.print(" periodic="); pw.print(status.numSourcePeriodic); + pw.print(" server="); pw.print(status.numSourceServer); + pw.print(" user="); pw.print(status.numSourceUser); + pw.print(" total="); pw.print(status.numSyncs); + pw.println(); + pw.print(" total duration: "); + pw.println(DateUtils.formatElapsedTime(status.totalElapsedTime/1000)); + if (status.lastSuccessTime != 0) { + pw.print(" SUCCESS: source="); + pw.print(SyncStorageEngine.SOURCES[status.lastSuccessSource]); + pw.print(" time="); + pw.println(formatTime(status.lastSuccessTime)); + } + if (status.lastFailureTime != 0) { + pw.print(" FAILURE: source="); + pw.print(SyncStorageEngine.SOURCES[ + status.lastFailureSource]); + pw.print(" initialTime="); + pw.print(formatTime(status.initialFailureTime)); + pw.print(" lastTime="); + pw.println(formatTime(status.lastFailureTime)); + pw.print(" message: "); pw.println(status.lastFailureMesg); } } } @@ -1580,6 +1448,36 @@ public class SyncManager implements OnAccountsUpdateListener { } } + private boolean isSyncAllowed(Account account, String authority, boolean manualSync, + boolean backgroundDataUsageAllowed) { + Account[] accounts = mAccounts; + + // Sync is disabled, drop this operation. + if (!isSyncEnabled()) { + return false; + } + + // skip the sync if the account of this operation no longer exists + if (accounts == null || !ArrayUtils.contains(accounts, account)) { + return false; + } + + // skip the sync if it isn't manual and auto sync is disabled + final boolean syncAutomatically = + mSyncStorageEngine.getSyncAutomatically(account, authority) + && mSyncStorageEngine.getMasterSyncAutomatically(); + if (!(manualSync || (backgroundDataUsageAllowed && syncAutomatically))) { + return false; + } + + if (mSyncStorageEngine.getIsSyncable(account, authority) <= 0) { + // if not syncable or if the syncable is unknown (< 0), don't allow + return false; + } + + return true; + } + private void runStateSyncing() { // if the sync timeout has been reached then cancel it @@ -1589,7 +1487,7 @@ public class SyncManager implements OnAccountsUpdateListener { if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) { SyncOperation nextSyncOperation; synchronized (mSyncQueue) { - nextSyncOperation = mSyncQueue.nextReadyToRun(now); + nextSyncOperation = getNextReadyToRunSyncOperation(now); } if (nextSyncOperation != null) { Log.d(TAG, "canceling and rescheduling sync because it ran too long: " @@ -1643,7 +1541,7 @@ public class SyncManager implements OnAccountsUpdateListener { synchronized (mSyncQueue) { final long now = SystemClock.elapsedRealtime(); while (true) { - op = mSyncQueue.nextReadyToRun(now); + op = getNextReadyToRunSyncOperation(now); if (op == null) { if (isLoggable) { Log.v(TAG, "runStateIdle: no more sync operations, returning"); @@ -1655,42 +1553,9 @@ public class SyncManager implements OnAccountsUpdateListener { // from the queue now mSyncQueue.remove(op); - // Sync is disabled, drop this operation. - if (!isSyncEnabled()) { - if (isLoggable) { - Log.v(TAG, "runStateIdle: sync disabled, dropping " + op); - } - continue; - } - - // skip the sync if the account of this operation no longer exists - if (!ArrayUtils.contains(accounts, op.account)) { - if (isLoggable) { - Log.v(TAG, "runStateIdle: account not present, dropping " + op); - } - continue; - } - - // skip the sync if it isn't manual and auto sync is disabled - final boolean manualSync = - op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); - final boolean syncAutomatically = - mSyncStorageEngine.getSyncAutomatically(op.account, op.authority) - && mSyncStorageEngine.getMasterSyncAutomatically(); - if (!(manualSync || (backgroundDataUsageAllowed && syncAutomatically))) { - if (isLoggable) { - Log.v(TAG, "runStateIdle: sync of this operation is not allowed, " - + "dropping " + op); - } - continue; - } - - if (mSyncStorageEngine.getIsSyncable(op.account, op.authority) <= 0) { - // if not syncable or if the syncable is unknown (< 0), don't allow - if (isLoggable) { - Log.v(TAG, "runStateIdle: sync of this operation is not allowed, " - + "dropping " + op); - } + if (!isSyncAllowed(op.account, op.authority, + op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false), + backgroundDataUsageAllowed)) { continue; } @@ -1736,6 +1601,74 @@ public class SyncManager implements OnAccountsUpdateListener { // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message } + private SyncOperation getNextPeriodicSyncOperation() { + final boolean backgroundDataUsageAllowed = + getConnectivityManager().getBackgroundDataSetting(); + SyncStorageEngine.AuthorityInfo best = null; + long bestPollTimeAbsolute = Long.MAX_VALUE; + Bundle bestExtras = null; + ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities(); + for (SyncStorageEngine.AuthorityInfo info : infos) { + if (!isSyncAllowed(info.account, info.authority, false /* manualSync */, + backgroundDataUsageAllowed)) { + continue; + } + SyncStatusInfo status = mSyncStorageEngine.getStatusByAccountAndAuthority( + info.account, info.authority); + int i = 0; + for (Pair<Bundle, Long> periodicSync : info.periodicSyncs) { + long lastPollTimeAbsolute = status != null ? status.getPeriodicSyncTime(i) : 0; + final Bundle extras = periodicSync.first; + final Long periodInSeconds = periodicSync.second; + long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000; + if (nextPollTimeAbsolute < bestPollTimeAbsolute) { + best = info; + bestPollTimeAbsolute = nextPollTimeAbsolute; + bestExtras = extras; + } + i++; + } + } + + if (best == null) { + return null; + } + + final long nowAbsolute = System.currentTimeMillis(); + final SyncOperation syncOperation = new SyncOperation(best.account, + SyncStorageEngine.SOURCE_PERIODIC, + best.authority, bestExtras, 0 /* delay */); + syncOperation.earliestRunTime = SystemClock.elapsedRealtime() + + (bestPollTimeAbsolute - nowAbsolute); + if (syncOperation.earliestRunTime < 0) { + syncOperation.earliestRunTime = 0; + } + return syncOperation; + } + + public Pair<SyncOperation, Long> bestSyncOperationCandidate() { + Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation(); + SyncOperation nextOp = nextOpAndRunTime != null ? nextOpAndRunTime.first : null; + Long nextRunTime = nextOpAndRunTime != null ? nextOpAndRunTime.second : null; + SyncOperation pollOp = getNextPeriodicSyncOperation(); + if (nextOp != null + && (pollOp == null || nextOp.expedited + || nextRunTime <= pollOp.earliestRunTime)) { + return nextOpAndRunTime; + } else if (pollOp != null) { + return Pair.create(pollOp, pollOp.earliestRunTime); + } else { + return null; + } + } + + private SyncOperation getNextReadyToRunSyncOperation(long now) { + Pair<SyncOperation, Long> nextOpAndRunTime = bestSyncOperationCandidate(); + return nextOpAndRunTime != null && nextOpAndRunTime.second <= now + ? nextOpAndRunTime.first + : null; + } + private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) { mActiveSyncContext.mSyncAdapter = syncAdapter; final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation; @@ -1961,7 +1894,8 @@ public class SyncManager implements OnAccountsUpdateListener { ActiveSyncContext activeSyncContext = mActiveSyncContext; if (activeSyncContext == null) { synchronized (mSyncQueue) { - alarmTime = mSyncQueue.nextRunTime(now); + Pair<SyncOperation, Long> candidate = bestSyncOperationCandidate(); + alarmTime = candidate != null ? candidate.second : 0; } } else { final long notificationTime = @@ -2102,9 +2036,22 @@ public class SyncManager implements OnAccountsUpdateListener { SyncStorageEngine.EVENT_STOP, syncOperation.syncSource, syncOperation.account.name.hashCode()); - mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage, - downstreamActivity, upstreamActivity); + mSyncStorageEngine.stopSyncEvent(rowId, syncOperation.extras, elapsedTime, + resultMessage, downstreamActivity, upstreamActivity); } } + public static long runTimeWithBackoffs(SyncStorageEngine syncStorageEngine, + Account account, String authority, boolean isManualSync, long runTime) { + // if this is a manual sync, the run time is unchanged + // otherwise, the run time is the max of the backoffs and the run time. + if (isManualSync) { + return runTime; + } + + Pair<Long, Long> backoff = syncStorageEngine.getBackoff(account, authority); + long delayUntilTime = syncStorageEngine.getDelayUntilTime(account, authority); + + return Math.max(Math.max(runTime, delayUntilTime), backoff != null ? backoff.first : 0); + } } diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java index a9f15d9f9c26..2eead3abdbf5 100644 --- a/core/java/android/content/SyncQueue.java +++ b/core/java/android/content/SyncQueue.java @@ -2,8 +2,6 @@ package android.content; import com.google.android.collect.Maps; -import android.os.Bundle; -import android.os.SystemClock; import android.util.Pair; import android.util.Log; import android.accounts.Account; @@ -32,10 +30,9 @@ public class SyncQueue { final int N = ops.size(); for (int i=0; i<N; i++) { SyncStorageEngine.PendingOperation op = ops.get(i); - // -1 is a special value that means expedited - final int delay = op.expedited ? -1 : 0; SyncOperation syncOperation = new SyncOperation( - op.account, op.syncSource, op.authority, op.extras, delay); + op.account, op.syncSource, op.authority, op.extras, 0 /* delay */); + syncOperation.expedited = op.expedited; syncOperation.pendingOperation = op; add(syncOperation, op); } @@ -90,8 +87,15 @@ public class SyncQueue { return true; } + /** + * Remove the specified operation if it is in the queue. + * @param operation the operation to remove + */ public void remove(SyncOperation operation) { SyncOperation operationToRemove = mOperationsMap.remove(operation.key); + if (operationToRemove == null) { + return; + } if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) { final String errorMessage = "unable to find pending row for " + operationToRemove; Log.e(TAG, errorMessage, new IllegalStateException(errorMessage)); @@ -102,54 +106,30 @@ public class SyncQueue { * Find the operation that should run next. Operations are sorted by their earliestRunTime, * prioritizing expedited operations. The earliestRunTime is adjusted by the sync adapter's * backoff and delayUntil times, if any. - * @param now the current {@link android.os.SystemClock#elapsedRealtime()} * @return the operation that should run next and when it should run. The time may be in * the future. It is expressed in milliseconds since boot. */ - private Pair<SyncOperation, Long> nextOperation(long now) { - SyncOperation lowestOp = null; - long lowestOpRunTime = 0; + public Pair<SyncOperation, Long> nextOperation() { + SyncOperation best = null; + long bestRunTime = 0; for (SyncOperation op : mOperationsMap.values()) { - // effectiveRunTime: - // - backoffTime > currentTime : backoffTime - // - backoffTime <= currentTime : op.runTime - Pair<Long, Long> backoff = null; - long delayUntilTime = 0; - final boolean isManualSync = - op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false); - if (!isManualSync) { - backoff = mSyncStorageEngine.getBackoff(op.account, op.authority); - delayUntilTime = mSyncStorageEngine.getDelayUntilTime(op.account, op.authority); - } - long backoffTime = Math.max(backoff != null ? backoff.first : 0, delayUntilTime); - long opRunTime = backoffTime > now ? backoffTime : op.earliestRunTime; - if (lowestOp == null - || (lowestOp.expedited == op.expedited - ? opRunTime < lowestOpRunTime - : op.expedited)) { - lowestOp = op; - lowestOpRunTime = opRunTime; + long opRunTime = SyncManager.runTimeWithBackoffs(mSyncStorageEngine, op.account, + op.authority, + op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false), + op.earliestRunTime); + // if the expedited state of both ops are the same then compare their runtime. + // Otherwise the candidate is only better than the current best if the candidate + // is expedited. + if (best == null + || (best.expedited == op.expedited ? opRunTime < bestRunTime : op.expedited)) { + best = op; + bestRunTime = opRunTime; } } - if (lowestOp == null) { - return null; - } - return Pair.create(lowestOp, lowestOpRunTime); - } - - /** - * Return when the next SyncOperation will be ready to run or null if there are - * none. - * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to - * decide if the sync operation is ready to run - * @return when the next SyncOperation will be ready to run, expressed in elapsedRealtime() - */ - public Long nextRunTime(long now) { - Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now); - if (nextOpAndRunTime == null) { + if (best == null) { return null; } - return nextOpAndRunTime.second; + return Pair.create(best, bestRunTime); } /** @@ -158,21 +138,25 @@ public class SyncQueue { * decide if the sync operation is ready to run * @return the SyncOperation that should be run next and is ready to run. */ - public SyncOperation nextReadyToRun(long now) { - Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now); + public Pair<SyncOperation, Long> nextReadyToRun(long now) { + Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(); if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) { return null; } - return nextOpAndRunTime.first; + return nextOpAndRunTime; } - public void clear(Account account, String authority) { + public void remove(Account account, String authority) { Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator(); while (entries.hasNext()) { Map.Entry<String, SyncOperation> entry = entries.next(); SyncOperation syncOperation = entry.getValue(); - if (account != null && !syncOperation.account.equals(account)) continue; - if (authority != null && !syncOperation.authority.equals(authority)) continue; + if (account != null && !syncOperation.account.equals(account)) { + continue; + } + if (authority != null && !syncOperation.authority.equals(authority)) { + continue; + } entries.remove(); if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) { final String errorMessage = "unable to find pending row for " + syncOperation; diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java index b8fda030b11c..bb2b2dace8bf 100644 --- a/core/java/android/content/SyncStatusInfo.java +++ b/core/java/android/content/SyncStatusInfo.java @@ -20,10 +20,12 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.util.ArrayList; + /** @hide */ public class SyncStatusInfo implements Parcelable { - static final int VERSION = 1; - + static final int VERSION = 2; + public final int authorityId; public long totalElapsedTime; public int numSyncs; @@ -31,6 +33,7 @@ public class SyncStatusInfo implements Parcelable { public int numSourceServer; public int numSourceLocal; public int numSourceUser; + public int numSourcePeriodic; public long lastSuccessTime; public int lastSuccessSource; public long lastFailureTime; @@ -39,7 +42,10 @@ public class SyncStatusInfo implements Parcelable { public long initialFailureTime; public boolean pending; public boolean initialize; - + public ArrayList<Long> periodicSyncTimes; + + private static final String TAG = "Sync"; + SyncStatusInfo(int authorityId) { this.authorityId = authorityId; } @@ -50,10 +56,11 @@ public class SyncStatusInfo implements Parcelable { return Integer.parseInt(lastFailureMesg); } } catch (NumberFormatException e) { + Log.d(TAG, "error parsing lastFailureMesg of " + lastFailureMesg, e); } return def; } - + public int describeContents() { return 0; } @@ -75,11 +82,19 @@ public class SyncStatusInfo implements Parcelable { parcel.writeLong(initialFailureTime); parcel.writeInt(pending ? 1 : 0); parcel.writeInt(initialize ? 1 : 0); + if (periodicSyncTimes != null) { + parcel.writeInt(periodicSyncTimes.size()); + for (long periodicSyncTime : periodicSyncTimes) { + parcel.writeLong(periodicSyncTime); + } + } else { + parcel.writeInt(-1); + } } SyncStatusInfo(Parcel parcel) { int version = parcel.readInt(); - if (version != VERSION) { + if (version != VERSION && version != 1) { Log.w("SyncStatusInfo", "Unknown version: " + version); } authorityId = parcel.readInt(); @@ -97,8 +112,51 @@ public class SyncStatusInfo implements Parcelable { initialFailureTime = parcel.readLong(); pending = parcel.readInt() != 0; initialize = parcel.readInt() != 0; + if (version == 1) { + periodicSyncTimes = null; + } else { + int N = parcel.readInt(); + if (N < 0) { + periodicSyncTimes = null; + } else { + periodicSyncTimes = new ArrayList<Long>(); + for (int i=0; i<N; i++) { + periodicSyncTimes.add(parcel.readLong()); + } + } + } } - + + public void setPeriodicSyncTime(int index, long when) { + ensurePeriodicSyncTimeSize(index); + periodicSyncTimes.set(index, when); + } + + private void ensurePeriodicSyncTimeSize(int index) { + if (periodicSyncTimes == null) { + periodicSyncTimes = new ArrayList<Long>(0); + } + + final int requiredSize = index + 1; + if (periodicSyncTimes.size() < requiredSize) { + for (int i = periodicSyncTimes.size(); i < requiredSize; i++) { + periodicSyncTimes.add((long) 0); + } + } + } + + public long getPeriodicSyncTime(int index) { + if (periodicSyncTimes == null || periodicSyncTimes.size() < (index + 1)) { + return 0; + } + return periodicSyncTimes.get(index); + } + + public void removePeriodicSyncTime(int index) { + ensurePeriodicSyncTimeSize(index); + periodicSyncTimes.remove(index); + } + public static final Creator<SyncStatusInfo> CREATOR = new Creator<SyncStatusInfo>() { public SyncStatusInfo createFromParcel(Parcel in) { return new SyncStatusInfo(in); diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index db70096f535c..07a1f46aa91b 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -36,7 +36,6 @@ import android.os.Message; import android.os.Parcel; import android.os.RemoteCallbackList; import android.os.RemoteException; -import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import android.util.Xml; @@ -50,6 +49,7 @@ import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.TimeZone; +import java.util.List; /** * Singleton that tracks the sync data and overall sync @@ -62,6 +62,8 @@ public class SyncStorageEngine extends Handler { private static final boolean DEBUG = false; private static final boolean DEBUG_FILE = false; + private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day + // @VisibleForTesting static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4; @@ -89,6 +91,9 @@ public class SyncStorageEngine extends Handler { /** Enum value for a user-initiated sync. */ public static final int SOURCE_USER = 3; + /** Enum value for a periodic sync. */ + public static final int SOURCE_PERIODIC = 4; + public static final long NOT_IN_BACKOFF_MODE = -1; private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT = @@ -99,7 +104,8 @@ public class SyncStorageEngine extends Handler { public static final String[] SOURCES = { "SERVER", "LOCAL", "POLL", - "USER" }; + "USER", + "PERIODIC" }; // The MESG column will contain one of these or one of the Error types. public static final String MESG_SUCCESS = "success"; @@ -164,6 +170,7 @@ public class SyncStorageEngine extends Handler { long backoffTime; long backoffDelay; long delayUntil; + final ArrayList<Pair<Bundle, Long>> periodicSyncs; AuthorityInfo(Account account, String authority, int ident) { this.account = account; @@ -173,6 +180,8 @@ public class SyncStorageEngine extends Handler { syncable = -1; // default to "unknown" backoffTime = -1; // if < 0 then we aren't in backoff mode backoffDelay = -1; // if < 0 then we aren't in backoff mode + periodicSyncs = new ArrayList<Pair<Bundle, Long>>(); + periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS)); } } @@ -228,6 +237,7 @@ public class SyncStorageEngine extends Handler { private int mYearInDays; private final Context mContext; + private static volatile SyncStorageEngine sSyncStorageEngine = null; /** @@ -262,17 +272,15 @@ public class SyncStorageEngine extends Handler { private int mNextHistoryId = 0; private boolean mMasterSyncAutomatically = true; - private SyncStorageEngine(Context context) { + private SyncStorageEngine(Context context, File dataDir) { mContext = context; sSyncStorageEngine = this; mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0")); - // This call will return the correct directory whether Encrypted File Systems is - // enabled or not. - File dataDir = Environment.getSecureDataDirectory(); File systemDir = new File(dataDir, "system"); File syncDir = new File(systemDir, "sync"); + syncDir.mkdirs(); mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); mPendingFile = new AtomicFile(new File(syncDir, "pending.bin")); @@ -286,14 +294,17 @@ public class SyncStorageEngine extends Handler { } public static SyncStorageEngine newTestInstance(Context context) { - return new SyncStorageEngine(context); + return new SyncStorageEngine(context, context.getFilesDir()); } public static void init(Context context) { if (sSyncStorageEngine != null) { return; } - sSyncStorageEngine = new SyncStorageEngine(context); + // This call will return the correct directory whether Encrypted File Systems is + // enabled or not. + File dataDir = Environment.getSecureDataDirectory(); + sSyncStorageEngine = new SyncStorageEngine(context, dataDir); } public static SyncStorageEngine getSingleton() { @@ -475,7 +486,7 @@ public class SyncStorageEngine extends Handler { } } else { AuthorityInfo authority = - getOrCreateAuthorityLocked(account, providerName, -1, false); + getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true); if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) { return; } @@ -483,9 +494,6 @@ public class SyncStorageEngine extends Handler { authority.backoffDelay = nextDelay; changed = true; } - if (changed) { - writeAccountInfoLocked(); - } } if (changed) { @@ -499,12 +507,12 @@ public class SyncStorageEngine extends Handler { + " -> delayUntil " + delayUntil); } synchronized (mAuthorities) { - AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); + AuthorityInfo authority = getOrCreateAuthorityLocked( + account, providerName, -1 /* ident */, true); if (authority.delayUntil == delayUntil) { return; } authority.delayUntil = delayUntil; - writeAccountInfoLocked(); } reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); @@ -520,6 +528,90 @@ public class SyncStorageEngine extends Handler { } } + private void updateOrRemovePeriodicSync(Account account, String providerName, Bundle extras, + long period, boolean add) { + if (period <= 0) { + period = 0; + } + if (extras == null) { + extras = new Bundle(); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", provider " + providerName + + " -> period " + period + ", extras " + extras); + } + synchronized (mAuthorities) { + AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false); + if (add) { + boolean alreadyPresent = false; + for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) { + Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i); + final Bundle existingExtras = syncInfo.first; + if (equals(existingExtras, extras)) { + if (syncInfo.second == period) { + return; + } + authority.periodicSyncs.set(i, Pair.create(extras, period)); + alreadyPresent = true; + break; + } + } + if (!alreadyPresent) { + authority.periodicSyncs.add(Pair.create(extras, period)); + SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); + status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0); + } + } else { + SyncStatusInfo status = mSyncStatus.get(authority.ident); + boolean changed = false; + Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator(); + int i = 0; + while (iterator.hasNext()) { + Pair<Bundle, Long> syncInfo = iterator.next(); + if (equals(syncInfo.first, extras)) { + iterator.remove(); + changed = true; + if (status != null) { + status.removePeriodicSyncTime(i); + } + } else { + i++; + } + } + if (!changed) { + return; + } + } + writeAccountInfoLocked(); + writeStatusLocked(); + } + + reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); + } + + public void addPeriodicSync(Account account, String providerName, Bundle extras, + long pollFrequency) { + updateOrRemovePeriodicSync(account, providerName, extras, pollFrequency, true /* add */); + } + + public void removePeriodicSync(Account account, String providerName, Bundle extras) { + updateOrRemovePeriodicSync(account, providerName, extras, 0 /* period, ignored */, + false /* remove */); + } + + public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) { + ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>(); + synchronized (mAuthorities) { + AuthorityInfo authority = getAuthorityLocked(account, providerName, "getPeriodicSyncs"); + if (authority != null) { + for (Pair<Bundle, Long> item : authority.periodicSyncs) { + syncs.add(new PeriodicSync(account, providerName, item.first, item.second)); + } + } + } + return syncs; + } + public void setMasterSyncAutomatically(boolean flag) { boolean old; synchronized (mAuthorities) { @@ -817,7 +909,25 @@ public class SyncStorageEngine extends Handler { return id; } - public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage, + public static boolean equals(Bundle b1, Bundle b2) { + if (b1.size() != b2.size()) { + return false; + } + if (b1.isEmpty()) { + return true; + } + for (String key : b1.keySet()) { + if (!b2.containsKey(key)) { + return false; + } + if (!b1.get(key).equals(b2.get(key))) { + return false; + } + } + return true; + } + + public void stopSyncEvent(long historyId, Bundle extras, long elapsedTime, String resultMessage, long downstreamActivity, long upstreamActivity) { synchronized (mAuthorities) { if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId); @@ -860,6 +970,17 @@ public class SyncStorageEngine extends Handler { case SOURCE_SERVER: status.numSourceServer++; break; + case SOURCE_PERIODIC: + status.numSourcePeriodic++; + AuthorityInfo authority = mAuthorities.get(item.authorityId); + for (int periodicSyncIndex = 0; + periodicSyncIndex < authority.periodicSyncs.size(); + periodicSyncIndex++) { + if (equals(extras, authority.periodicSyncs.get(periodicSyncIndex).first)) { + status.setPeriodicSyncTime(periodicSyncIndex, item.eventTime); + } + } + break; } boolean writeStatisticsNow = false; @@ -948,11 +1069,27 @@ public class SyncStorageEngine extends Handler { } /** + * Return an array of the current authorities. Note + * that the objects inside the array are the real, live objects, + * so be careful what you do with them. + */ + public ArrayList<AuthorityInfo> getAuthorities() { + synchronized (mAuthorities) { + final int N = mAuthorities.size(); + ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N); + for (int i=0; i<N; i++) { + infos.add(mAuthorities.valueAt(i)); + } + return infos; + } + } + + /** * Returns the status that matches the authority and account. * * @param account the account we want to check * @param authority the authority whose row should be selected - * @return the SyncStatusInfo for the authority, or null if none exists + * @return the SyncStatusInfo for the authority */ public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) { if (account == null || authority == null) { @@ -1130,6 +1267,12 @@ public class SyncStorageEngine extends Handler { return authority; } + public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) { + synchronized (mAuthorities) { + return getOrCreateSyncStatusLocked(authority.ident); + } + } + private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) { SyncStatusInfo status = mSyncStatus.get(authorityId); if (status == null) { @@ -1155,6 +1298,25 @@ public class SyncStorageEngine extends Handler { } /** + * public for testing + */ + public void clearAndReadState() { + synchronized (mAuthorities) { + mAuthorities.clear(); + mAccounts.clear(); + mPendingOperations.clear(); + mSyncStatus.clear(); + mSyncHistory.clear(); + + readAccountInfoLocked(); + readStatusLocked(); + readPendingOperationsLocked(); + readStatisticsLocked(); + readLegacyAccountInfoLocked(); + } + } + + /** * Read all account information back in to the initial engine state. */ private void readAccountInfoLocked() { @@ -1175,59 +1337,23 @@ public class SyncStorageEngine extends Handler { mMasterSyncAutomatically = listen == null || Boolean.parseBoolean(listen); eventType = parser.next(); + AuthorityInfo authority = null; + Pair<Bundle, Long> periodicSync = null; do { - if (eventType == XmlPullParser.START_TAG - && parser.getDepth() == 2) { + if (eventType == XmlPullParser.START_TAG) { tagName = parser.getName(); - if ("authority".equals(tagName)) { - int id = -1; - try { - id = Integer.parseInt(parser.getAttributeValue( - null, "id")); - } catch (NumberFormatException e) { - } catch (NullPointerException e) { + if (parser.getDepth() == 2) { + if ("authority".equals(tagName)) { + authority = parseAuthority(parser); + periodicSync = null; + } + } else if (parser.getDepth() == 3) { + if ("periodicSync".equals(tagName) && authority != null) { + periodicSync = parsePeriodicSync(parser, authority); } - if (id >= 0) { - String accountName = parser.getAttributeValue( - null, "account"); - String accountType = parser.getAttributeValue( - null, "type"); - if (accountType == null) { - accountType = "com.google"; - } - String authorityName = parser.getAttributeValue( - null, "authority"); - String enabled = parser.getAttributeValue( - null, "enabled"); - String syncable = parser.getAttributeValue(null, "syncable"); - AuthorityInfo authority = mAuthorities.get(id); - if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" - + accountName + " auth=" + authorityName - + " enabled=" + enabled - + " syncable=" + syncable); - if (authority == null) { - if (DEBUG_FILE) Log.v(TAG, "Creating entry"); - authority = getOrCreateAuthorityLocked( - new Account(accountName, accountType), - authorityName, id, false); - } - if (authority != null) { - authority.enabled = enabled == null - || Boolean.parseBoolean(enabled); - if ("unknown".equals(syncable)) { - authority.syncable = -1; - } else { - authority.syncable = - (syncable == null || Boolean.parseBoolean(enabled)) - ? 1 - : 0; - } - } else { - Log.w(TAG, "Failure adding authority: account=" - + accountName + " auth=" + authorityName - + " enabled=" + enabled - + " syncable=" + syncable); - } + } else if (parser.getDepth() == 4 && periodicSync != null) { + if ("extra".equals(tagName)) { + parseExtra(parser, periodicSync); } } } @@ -1249,6 +1375,105 @@ public class SyncStorageEngine extends Handler { } } + private AuthorityInfo parseAuthority(XmlPullParser parser) { + AuthorityInfo authority = null; + int id = -1; + try { + id = Integer.parseInt(parser.getAttributeValue( + null, "id")); + } catch (NumberFormatException e) { + Log.e(TAG, "error parsing the id of the authority", e); + } catch (NullPointerException e) { + Log.e(TAG, "the id of the authority is null", e); + } + if (id >= 0) { + String accountName = parser.getAttributeValue(null, "account"); + String accountType = parser.getAttributeValue(null, "type"); + if (accountType == null) { + accountType = "com.google"; + } + String authorityName = parser.getAttributeValue(null, "authority"); + String enabled = parser.getAttributeValue(null, "enabled"); + String syncable = parser.getAttributeValue(null, "syncable"); + authority = mAuthorities.get(id); + if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" + + accountName + " auth=" + authorityName + + " enabled=" + enabled + + " syncable=" + syncable); + if (authority == null) { + if (DEBUG_FILE) Log.v(TAG, "Creating entry"); + authority = getOrCreateAuthorityLocked( + new Account(accountName, accountType), authorityName, id, false); + // clear this since we will read these later on + authority.periodicSyncs.clear(); + } + if (authority != null) { + authority.enabled = enabled == null || Boolean.parseBoolean(enabled); + if ("unknown".equals(syncable)) { + authority.syncable = -1; + } else { + authority.syncable = + (syncable == null || Boolean.parseBoolean(enabled)) ? 1 : 0; + } + } else { + Log.w(TAG, "Failure adding authority: account=" + + accountName + " auth=" + authorityName + + " enabled=" + enabled + + " syncable=" + syncable); + } + } + + return authority; + } + + private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) { + Bundle extras = new Bundle(); + String periodValue = parser.getAttributeValue(null, "period"); + final long period; + try { + period = Long.parseLong(periodValue); + } catch (NumberFormatException e) { + Log.e(TAG, "error parsing the period of a periodic sync", e); + return null; + } catch (NullPointerException e) { + Log.e(TAG, "the period of a periodic sync is null", e); + return null; + } + final Pair<Bundle, Long> periodicSync = Pair.create(extras, period); + authority.periodicSyncs.add(periodicSync); + return periodicSync; + } + + private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) { + final Bundle extras = periodicSync.first; + String name = parser.getAttributeValue(null, "name"); + String type = parser.getAttributeValue(null, "type"); + String value1 = parser.getAttributeValue(null, "value1"); + String value2 = parser.getAttributeValue(null, "value2"); + + try { + if ("long".equals(type)) { + extras.putLong(name, Long.parseLong(value1)); + } else if ("integer".equals(type)) { + extras.putInt(name, Integer.parseInt(value1)); + } else if ("double".equals(type)) { + extras.putDouble(name, Double.parseDouble(value1)); + } else if ("float".equals(type)) { + extras.putFloat(name, Float.parseFloat(value1)); + } else if ("boolean".equals(type)) { + extras.putBoolean(name, Boolean.parseBoolean(value1)); + } else if ("string".equals(type)) { + extras.putString(name, value1); + } else if ("account".equals(type)) { + extras.putParcelable(name, new Account(value1, value2)); + } + } catch (NumberFormatException e) { + Log.e(TAG, "error parsing bundle value", e); + } catch (NullPointerException e) { + Log.e(TAG, "error parsing bundle value", e); + } + } + /** * Write all account information to the account file. */ @@ -1284,6 +1509,41 @@ public class SyncStorageEngine extends Handler { } else if (authority.syncable == 0) { out.attribute(null, "syncable", "false"); } + for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) { + out.startTag(null, "periodicSync"); + out.attribute(null, "period", Long.toString(periodicSync.second)); + final Bundle extras = periodicSync.first; + for (String key : extras.keySet()) { + out.startTag(null, "extra"); + out.attribute(null, "name", key); + final Object value = extras.get(key); + if (value instanceof Long) { + out.attribute(null, "type", "long"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Integer) { + out.attribute(null, "type", "integer"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Boolean) { + out.attribute(null, "type", "boolean"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Float) { + out.attribute(null, "type", "float"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Double) { + out.attribute(null, "type", "double"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof String) { + out.attribute(null, "type", "string"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Account) { + out.attribute(null, "type", "account"); + out.attribute(null, "value1", ((Account)value).name); + out.attribute(null, "value2", ((Account)value).type); + } + out.endTag(null, "extra"); + } + out.endTag(null, "periodicSync"); + } out.endTag(null, "authority"); } @@ -1389,6 +1649,7 @@ public class SyncStorageEngine extends Handler { st.numSourcePoll = getIntColumn(c, "numSourcePoll"); st.numSourceServer = getIntColumn(c, "numSourceServer"); st.numSourceUser = getIntColumn(c, "numSourceUser"); + st.numSourcePeriodic = 0; st.lastSuccessSource = getIntColumn(c, "lastSuccessSource"); st.lastSuccessTime = getLongColumn(c, "lastSuccessTime"); st.lastFailureSource = getIntColumn(c, "lastFailureSource"); diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java index f64261c1d0f9..7776520f3cd7 100644 --- a/core/java/android/database/sqlite/SQLiteClosable.java +++ b/core/java/android/database/sqlite/SQLiteClosable.java @@ -29,7 +29,7 @@ public abstract class SQLiteClosable { synchronized(mLock) { if (mReferenceCount <= 0) { throw new IllegalStateException( - "attempt to acquire a reference on a close SQLiteClosable"); + "attempt to acquire a reference on an already-closed SQLiteClosable obj."); } mReferenceCount++; } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index e4b0191c1446..540f4445ba12 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -66,64 +66,60 @@ public class SQLiteDatabase extends SQLiteClosable { * Algorithms used in ON CONFLICT clause * http://www.sqlite.org/lang_conflict.html */ - public static final class ConflictAlgorithm { - /** - * When a constraint violation occurs, an immediate ROLLBACK occurs, - * thus ending the current transaction, and the command aborts with a - * return code of SQLITE_CONSTRAINT. If no transaction is active - * (other than the implied transaction that is created on every command) - * then this algorithm works the same as ABORT. - */ - public static final int ROLLBACK = 1; - - /** - * When a constraint violation occurs,no ROLLBACK is executed - * so changes from prior commands within the same transaction - * are preserved. This is the default behavior. - */ - public static final int ABORT = 2; + /** + * When a constraint violation occurs, an immediate ROLLBACK occurs, + * thus ending the current transaction, and the command aborts with a + * return code of SQLITE_CONSTRAINT. If no transaction is active + * (other than the implied transaction that is created on every command) + * then this algorithm works the same as ABORT. + */ + public static final int CONFLICT_ROLLBACK = 1; - /** - * When a constraint violation occurs, the command aborts with a return - * code SQLITE_CONSTRAINT. But any changes to the database that - * the command made prior to encountering the constraint violation - * are preserved and are not backed out. - */ - public static final int FAIL = 3; + /** + * When a constraint violation occurs,no ROLLBACK is executed + * so changes from prior commands within the same transaction + * are preserved. This is the default behavior. + */ + public static final int CONFLICT_ABORT = 2; - /** - * When a constraint violation occurs, the one row that contains - * the constraint violation is not inserted or changed. - * But the command continues executing normally. Other rows before and - * after the row that contained the constraint violation continue to be - * inserted or updated normally. No error is returned. - */ - public static final int IGNORE = 4; + /** + * When a constraint violation occurs, the command aborts with a return + * code SQLITE_CONSTRAINT. But any changes to the database that + * the command made prior to encountering the constraint violation + * are preserved and are not backed out. + */ + public static final int CONFLICT_FAIL = 3; - /** - * When a UNIQUE constraint violation occurs, the pre-existing rows that - * are causing the constraint violation are removed prior to inserting - * or updating the current row. Thus the insert or update always occurs. - * The command continues executing normally. No error is returned. - * If a NOT NULL constraint violation occurs, the NULL value is replaced - * by the default value for that column. If the column has no default - * value, then the ABORT algorithm is used. If a CHECK constraint - * violation occurs then the IGNORE algorithm is used. When this conflict - * resolution strategy deletes rows in order to satisfy a constraint, - * it does not invoke delete triggers on those rows. - * This behavior might change in a future release. - */ - public static final int REPLACE = 5; + /** + * When a constraint violation occurs, the one row that contains + * the constraint violation is not inserted or changed. + * But the command continues executing normally. Other rows before and + * after the row that contained the constraint violation continue to be + * inserted or updated normally. No error is returned. + */ + public static final int CONFLICT_IGNORE = 4; - /** - * use the following when no conflict action is specified. - */ - public static final int NONE = 0; - private static final String[] VALUES = new String[] - {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; + /** + * When a UNIQUE constraint violation occurs, the pre-existing rows that + * are causing the constraint violation are removed prior to inserting + * or updating the current row. Thus the insert or update always occurs. + * The command continues executing normally. No error is returned. + * If a NOT NULL constraint violation occurs, the NULL value is replaced + * by the default value for that column. If the column has no default + * value, then the ABORT algorithm is used. If a CHECK constraint + * violation occurs then the IGNORE algorithm is used. When this conflict + * resolution strategy deletes rows in order to satisfy a constraint, + * it does not invoke delete triggers on those rows. + * This behavior might change in a future release. + */ + public static final int CONFLICT_REPLACE = 5; - private ConflictAlgorithm() {} // disable instantiation of this class - } + /** + * use the following when no conflict action is specified. + */ + public static final int CONFLICT_NONE = 0; + private static final String[] CONFLICT_VALUES = new String[] + {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "}; /** * Maximum Length Of A LIKE Or GLOB Pattern @@ -290,10 +286,6 @@ public class SQLiteDatabase extends SQLiteClosable { @Override protected void onAllReferencesReleased() { if (isOpen()) { - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.d(TAG, "captured_sql|" + mPath + "|DETACH DATABASE " + - getDatabaseName(mPath) + ";"); - } if (SQLiteDebug.DEBUG_SQL_CACHE) { mTimeClosed = getTime(); } @@ -782,7 +774,14 @@ public class SQLiteDatabase extends SQLiteClosable { SQLiteDatabase db = null; try { // Open the database. - return new SQLiteDatabase(path, factory, flags); + SQLiteDatabase sqliteDatabase = new SQLiteDatabase(path, factory, flags); + if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { + sqliteDatabase.enableSqlTracing(path); + } + if (SQLiteDebug.DEBUG_SQL_TIME) { + sqliteDatabase.enableSqlProfiling(path); + } + return sqliteDatabase; } catch (SQLiteDatabaseCorruptException e) { // Try to recover from this, if we can. // TODO: should we do this for other open failures? @@ -1338,7 +1337,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ public long insert(String table, String nullColumnHack, ContentValues values) { try { - return insertWithOnConflict(table, nullColumnHack, values, ConflictAlgorithm.NONE); + return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } catch (SQLException e) { Log.e(TAG, "Error inserting " + values, e); return -1; @@ -1360,7 +1359,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ public long insertOrThrow(String table, String nullColumnHack, ContentValues values) throws SQLException { - return insertWithOnConflict(table, nullColumnHack, values, ConflictAlgorithm.NONE); + return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } /** @@ -1377,7 +1376,7 @@ public class SQLiteDatabase extends SQLiteClosable { public long replace(String table, String nullColumnHack, ContentValues initialValues) { try { return insertWithOnConflict(table, nullColumnHack, initialValues, - ConflictAlgorithm.REPLACE); + CONFLICT_REPLACE); } catch (SQLException e) { Log.e(TAG, "Error inserting " + initialValues, e); return -1; @@ -1399,7 +1398,7 @@ public class SQLiteDatabase extends SQLiteClosable { public long replaceOrThrow(String table, String nullColumnHack, ContentValues initialValues) throws SQLException { return insertWithOnConflict(table, nullColumnHack, initialValues, - ConflictAlgorithm.REPLACE); + CONFLICT_REPLACE); } /** @@ -1412,10 +1411,10 @@ public class SQLiteDatabase extends SQLiteClosable { * @param initialValues this map contains the initial column values for the * row. The keys should be the column names and the values the * column values - * @param conflictAlgorithm {@link ConflictAlgorithm} for insert conflict resolver + * @param conflictAlgorithm for insert conflict resolver * @return the row ID of the newly inserted row * OR the primary key of the existing row if the input param 'conflictAlgorithm' = - * {@link ConflictAlgorithm#IGNORE} + * {@link #CONFLICT_IGNORE} * OR -1 if any error */ public long insertWithOnConflict(String table, String nullColumnHack, @@ -1427,7 +1426,7 @@ public class SQLiteDatabase extends SQLiteClosable { // Measurements show most sql lengths <= 152 StringBuilder sql = new StringBuilder(152); sql.append("INSERT"); - sql.append(ConflictAlgorithm.VALUES[conflictAlgorithm]); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); sql.append(" INTO "); sql.append(table); // Measurements show most values lengths < 40 @@ -1551,7 +1550,7 @@ public class SQLiteDatabase extends SQLiteClosable { * @return the number of rows affected */ public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { - return updateWithOnConflict(table, values, whereClause, whereArgs, ConflictAlgorithm.NONE); + return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); } /** @@ -1562,7 +1561,7 @@ public class SQLiteDatabase extends SQLiteClosable { * valid value that will be translated to NULL. * @param whereClause the optional WHERE clause to apply when updating. * Passing null will update all rows. - * @param conflictAlgorithm {@link ConflictAlgorithm} for update conflict resolver + * @param conflictAlgorithm for update conflict resolver * @return the number of rows affected */ public int updateWithOnConflict(String table, ContentValues values, @@ -1577,7 +1576,7 @@ public class SQLiteDatabase extends SQLiteClosable { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); - sql.append(ConflictAlgorithm.VALUES[conflictAlgorithm]); + sql.append(CONFLICT_VALUES[conflictAlgorithm]); sql.append(table); sql.append(" SET "); @@ -1652,9 +1651,6 @@ public class SQLiteDatabase extends SQLiteClosable { */ public void execSQL(String sql) throws SQLException { long timeStart = Debug.threadCpuTimeNanos(); - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.v(TAG, SQLiteDebug.captureSql(this.getPath(), sql, null)); - } lock(); try { native_execSQL(sql); @@ -1680,9 +1676,6 @@ public class SQLiteDatabase extends SQLiteClosable { if (bindArgs == null) { throw new IllegalArgumentException("Empty bindArgs"); } - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.v(TAG, SQLiteDebug.captureSql(this.getPath(), sql, bindArgs)); - } long timeStart = Debug.threadCpuTimeNanos(); lock(); SQLiteStatement statement = null; @@ -1741,10 +1734,6 @@ public class SQLiteDatabase extends SQLiteClosable { mLeakedException = new IllegalStateException(path + " SQLiteDatabase created and never closed"); mFactory = factory; - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.d(TAG, "captured_sql|" + mPath + "|ATTACH DATABASE '" + mPath + - "' as " + getDatabaseName(mPath) + ";"); - } dbopen(mPath, mFlags); if (SQLiteDebug.DEBUG_SQL_CACHE) { mTimeOpened = getTime(); @@ -1754,10 +1743,6 @@ public class SQLiteDatabase extends SQLiteClosable { setLocale(Locale.getDefault()); } catch (RuntimeException e) { Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e); - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.d(TAG, "captured_sql|" + mPath + "|DETACH DATABASE " + - getDatabaseName(mPath) + ";"); - } dbclose(); if (SQLiteDebug.DEBUG_SQL_CACHE) { mTimeClosed = getTime(); @@ -1770,20 +1755,6 @@ public class SQLiteDatabase extends SQLiteClosable { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis()); } - private String getDatabaseName(String path) { - if (path == null || path.trim().length() == 0) { - return "db not specified?"; - } - - if (path.equalsIgnoreCase(":memory:")) { - return "memorydb"; - } - String[] tokens = path.split("/"); - String[] lastNodeTokens = tokens[tokens.length - 1].split("\\.", 2); - return (lastNodeTokens.length == 1) ? lastNodeTokens[0] - : lastNodeTokens[0] + lastNodeTokens[1]; - } - /** * return whether the DB is opened as read only. * @return true if DB is opened as read only @@ -1922,8 +1893,9 @@ public class SQLiteDatabase extends SQLiteClosable { mCacheFullWarnings = 0; // clear the cache mCompiledQueries.clear(); - Log.w(TAG, "compiled-sql statement cache cleared for the database " + - getPath()); + Log.w(TAG, "Compiled-sql statement cache for database: " + + getPath() + " hit MAX size-limit too many times. " + + "Removing all compiled-sql statements from the cache."); } else { // clear just a single entry from cache Set<String> keySet = mCompiledQueries.keySet(); @@ -2061,6 +2033,23 @@ public class SQLiteDatabase extends SQLiteClosable { private native void dbopen(String path, int flags); /** + * Native call to setup tracing of all sql statements + * + * @param path the full path to the database + */ + private native void enableSqlTracing(String path); + + /** + * Native call to setup profiling of all sql statements. + * currently, sqlite's profiling = printing of execution-time + * (wall-clock time) of each of the sql statements, as they + * are executed. + * + * @param path the full path to the database + */ + private native void enableSqlProfiling(String path); + + /** * Native call to execute a raw SQL statement. {@link #lock} must be held * when calling this method. * diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java index b12034af1f60..4ea680e44b6f 100644 --- a/core/java/android/database/sqlite/SQLiteDebug.java +++ b/core/java/android/database/sqlite/SQLiteDebug.java @@ -31,17 +31,17 @@ public final class SQLiteDebug { Log.isLoggable("SQLiteStatements", Log.VERBOSE); /** - * Controls the printing of compiled-sql-statement cache stats. + * Controls the printing of wall-clock time taken to execute SQL statements + * as they are executed. */ - public static final boolean DEBUG_SQL_CACHE = - Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE); + public static final boolean DEBUG_SQL_TIME = + Log.isLoggable("SQLiteTime", Log.VERBOSE); /** - * Controls the capturing and printing of complete sql statement including the bind args and - * the database name. + * Controls the printing of compiled-sql-statement cache stats. */ - public static final boolean DEBUG_CAPTURE_SQL = - Log.isLoggable("SQLiteCaptureSql", Log.VERBOSE); + public static final boolean DEBUG_SQL_CACHE = + Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE); /** * Controls the stack trace reporting of active cursors being @@ -121,62 +121,4 @@ public final class SQLiteDebug { static synchronized void notifyActiveCursorFinalized() { sNumActiveCursorsFinalized++; } - - /** - * returns a message containing the given database name (path) and the string built by - * replacing "?" characters in the given sql string with the corresponding - * positional values from the given param bindArgs. - * - * @param path the database name - * @param sql sql string with possibly "?" for bindargs - * @param bindArgs args for "?"s in the above string - * @return the String to be logged - */ - /* package */ static String captureSql(String path, String sql, Object[] bindArgs) { - // how many bindargs in sql - sql = sql.trim(); - String args[] = sql.split("\\?"); - // how many "?"s in the given sql string? - int varArgsInSql = (sql.endsWith("?")) ? args.length : args.length - 1; - - // how many bind args do we have in the given input param bindArgs - int bindArgsLen = (bindArgs == null) ? 0 : bindArgs.length; - if (varArgsInSql < bindArgsLen) { - return "too many bindArgs provided. " + - "# of bindArgs = " + bindArgsLen + ", # of varargs = " + varArgsInSql + - "; sql = " + sql; - } - - // if there are no bindArgs, we are done. log the sql as is. - if (bindArgsLen == 0 && varArgsInSql == 0) { - return logSql(path, sql); - } - - StringBuilder buf = new StringBuilder(); - - // take the supplied bindArgs and plug them into sql - for (int i = 0; i < bindArgsLen; i++) { - buf.append(args[i]); - buf.append(bindArgs[i]); - } - - // does given sql have more varArgs than the supplied bindArgs - // if so, assign nulls to the extra varArgs in sql - for (int i = bindArgsLen; i < varArgsInSql; i ++) { - buf.append(args[i]); - buf.append("null"); - } - - // if there are any characters left in the given sql string AFTER the last "?" - // log them also. for example, if the given sql = "select * from test where a=? and b=1 - // then the following code appends " and b=1" string to buf. - if (varArgsInSql < args.length) { - buf.append(args[varArgsInSql]); - } - return logSql(path, buf.toString()); - } - - private static String logSql(String path, String sql) { - return "captured_sql|" + path + "|" + sql + ";"; - } } diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 1159c1de74c5..2d0aa3967a46 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -16,19 +16,10 @@ package android.database.sqlite; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import android.util.Log; - /** * A base class for compiled SQLite programs. */ public abstract class SQLiteProgram extends SQLiteClosable { - private static final String TAG = "SQLiteProgram"; /** The database this program is compiled against. */ protected SQLiteDatabase mDatabase; @@ -53,16 +44,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { */ protected int nStatement = 0; - /** - * stores all bindargs for debugging purposes - */ - private Map<Integer, String> mBindArgs = null; - /* package */ SQLiteProgram(SQLiteDatabase db, String sql) { - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - Log.d(TAG, "processing sql: " + sql); - } - mDatabase = db; mSql = sql; db.acquireReference(); @@ -120,7 +102,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { } /** - * @deprecated use this.compiledStatement.compile instead + * @deprecated This method is deprecated and must not be used. * * @param sql the SQL string to compile * @param forceCompilation forces the SQL to be recompiled in the event that there is an @@ -138,9 +120,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { * @param index The 1-based index to the parameter to bind null to */ public void bindNull(int index) { - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - addToBindArgs(index, "null"); - } acquireReference(); try { native_bind_null(index); @@ -157,9 +136,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { * @param value The value to bind */ public void bindLong(int index, long value) { - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - addToBindArgs(index, value + ""); - } acquireReference(); try { native_bind_long(index, value); @@ -176,9 +152,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { * @param value The value to bind */ public void bindDouble(int index, double value) { - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - addToBindArgs(index, value + ""); - } acquireReference(); try { native_bind_double(index, value); @@ -195,9 +168,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { * @param value The value to bind */ public void bindString(int index, String value) { - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - addToBindArgs(index, "'" + value + "'"); - } if (value == null) { throw new IllegalArgumentException("the bind value at index " + index + " is null"); } @@ -217,9 +187,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { * @param value The value to bind */ public void bindBlob(int index, byte[] value) { - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - addToBindArgs(index, "blob"); - } if (value == null) { throw new IllegalArgumentException("the bind value at index " + index + " is null"); } @@ -235,9 +202,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { * Clears all existing bindings. Unset bindings are treated as NULL. */ public void clearBindings() { - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - mBindArgs = null; - } acquireReference(); try { native_clear_bindings(); @@ -259,39 +223,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { } /** - * this method is called under the debug flag {@link SQLiteDebug.DEBUG_CAPTURE_SQL} only. - * it collects the bindargs as they are called by the callers the bind... methods in this - * class. - */ - private void addToBindArgs(int index, String obj) { - if (mBindArgs == null) { - mBindArgs = new HashMap<Integer, String>(); - } - mBindArgs.put(index, obj); - } - - /** - * constructs all the bindargs in sequence and returns a String Array of the values. - * it uses the HashMap built up by the above method. - * - * @return the string array of bindArgs with the args arranged in sequence - */ - /* package */ String[] getBindArgs() { - if (mBindArgs == null) { - return null; - } - Set<Integer> indexSet = mBindArgs.keySet(); - ArrayList<Integer> indexList = new ArrayList<Integer>(indexSet); - Collections.sort(indexList); - int len = indexList.size(); - String[] bindObjs = new String[len]; - for (int i = 0; i < len; i++) { - bindObjs[i] = mBindArgs.get(indexList.get(i)); - } - return bindObjs; - } - - /** + * @deprecated This method is deprecated and must not be used. * Compiles SQL into a SQLite program. * * <P>The database lock must be held when calling this method. @@ -299,6 +231,10 @@ public abstract class SQLiteProgram extends SQLiteClosable { */ @Deprecated protected final native void native_compile(String sql); + + /** + * @deprecated This method is deprecated and must not be used. + */ @Deprecated protected final native void native_finalize(); diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index c34661da8385..5bcad4b9a56e 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -18,7 +18,6 @@ package android.database.sqlite; import android.database.CursorWindow; import android.os.Debug; -import android.os.SystemClock; import android.util.Log; /** diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index 0cee3c526a08..f1f5a2ad2034 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -17,7 +17,6 @@ package android.database.sqlite; import android.os.Debug; -import android.util.Log; /** * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused. @@ -27,8 +26,6 @@ import android.util.Log; */ public class SQLiteStatement extends SQLiteProgram { - private static final String TAG = "SQLiteStatement"; - /** * Don't use SQLiteStatement constructor directly, please use * {@link SQLiteDatabase#compileStatement(String)} @@ -52,12 +49,6 @@ public class SQLiteStatement extends SQLiteProgram acquireReference(); try { - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - Log.v(TAG, "execute() for [" + mSql + "]"); - } - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs())); - } native_execute(); mDatabase.logTimeStat(mSql, timeStart); } finally { @@ -82,12 +73,6 @@ public class SQLiteStatement extends SQLiteProgram acquireReference(); try { - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - Log.v(TAG, "executeInsert() for [" + mSql + "]"); - } - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs())); - } native_execute(); mDatabase.logTimeStat(mSql, timeStart); return mDatabase.lastInsertRow(); @@ -111,12 +96,6 @@ public class SQLiteStatement extends SQLiteProgram acquireReference(); try { - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - Log.v(TAG, "simpleQueryForLong() for [" + mSql + "]"); - } - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs())); - } long retValue = native_1x1_long(); mDatabase.logTimeStat(mSql, timeStart); return retValue; @@ -140,12 +119,6 @@ public class SQLiteStatement extends SQLiteProgram acquireReference(); try { - if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { - Log.v(TAG, "simpleQueryForString() for [" + mSql + "]"); - } - if (SQLiteDebug.DEBUG_CAPTURE_SQL) { - Log.v(TAG, SQLiteDebug.captureSql(mDatabase.getPath(), mSql, getBindArgs())); - } String retValue = native_1x1_string(); mDatabase.logTimeStat(mSql, timeStart); return retValue; diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index b0c39093976b..cecacaa88503 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -620,7 +620,10 @@ public class KeyboardView extends View implements View.OnClickListener { if (mBuffer == null || mKeyboardChanged) { if (mBuffer == null || mKeyboardChanged && (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) { - mBuffer = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + // Make sure our bitmap is at least 1x1 + final int width = Math.max(1, getWidth()); + final int height = Math.max(1, getHeight()); + mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBuffer); } invalidateAllKeys(); diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java index f89ba9195ead..cb42d73d1aef 100644 --- a/core/java/android/provider/Calendar.java +++ b/core/java/android/provider/Calendar.java @@ -184,6 +184,20 @@ public final class Calendar { * <P>Type: INTEGER (long)</P> */ public static final String _SYNC_DIRTY = "_sync_dirty"; + + /** + * 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"; } /** @@ -579,20 +593,6 @@ public final class Calendar { public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/event_entities"); - /** - * 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 = "_sync_account"; - - /** - * 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 = "_sync_account_type"; - public static EntityIterator newEntityIterator(Cursor cursor, ContentResolver resolver) { return new EntityIteratorImpl(cursor, resolver); } diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java index 0d25b2fdd833..7915208ef2c1 100644 --- a/core/java/android/speech/RecognitionManager.java +++ b/core/java/android/speech/RecognitionManager.java @@ -216,7 +216,6 @@ public class RecognitionManager { throw new IllegalArgumentException("intent must not be null"); } checkIsCalledFromMainThread(); - checkIsCommandAllowed(); if (mConnection == null) { // first time connection mConnection = new Connection(); if (!mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), @@ -243,7 +242,6 @@ public class RecognitionManager { */ public void stopListening() { checkIsCalledFromMainThread(); - checkIsCommandAllowed(); putMessage(Message.obtain(mHandler, MSG_STOP)); } @@ -254,7 +252,6 @@ public class RecognitionManager { */ public void cancel() { checkIsCalledFromMainThread(); - checkIsCommandAllowed(); putMessage(Message.obtain(mHandler, MSG_CANCEL)); } @@ -265,12 +262,6 @@ public class RecognitionManager { } } - private void checkIsCommandAllowed() { - if (mService == null && mPendingTasks.isEmpty()) { // setListener message must be there - throw new IllegalStateException("Listener must be set before any command is called"); - } - } - private void putMessage(Message msg) { if (mService == null) { mPendingTasks.offer(msg); @@ -281,6 +272,9 @@ public class RecognitionManager { /** sends the actual message to the service */ private void handleStartListening(Intent recognizerIntent) { + if (!checkOpenConnection()) { + return; + } try { mService.startListening(recognizerIntent, mListener); if (DBG) Log.d(TAG, "service start listening command succeded"); @@ -292,6 +286,9 @@ public class RecognitionManager { /** sends the actual message to the service */ private void handleStopMessage() { + if (!checkOpenConnection()) { + return; + } try { mService.stopListening(mListener); if (DBG) Log.d(TAG, "service stop listening command succeded"); @@ -303,6 +300,9 @@ public class RecognitionManager { /** sends the actual message to the service */ private void handleCancelMessage() { + if (!checkOpenConnection()) { + return; + } try { mService.cancel(mListener); if (DBG) Log.d(TAG, "service cancel command succeded"); @@ -311,6 +311,15 @@ public class RecognitionManager { mListener.onError(ERROR_CLIENT); } } + + private boolean checkOpenConnection() { + if (mService != null) { + return true; + } + mListener.onError(ERROR_CLIENT); + Log.e(TAG, "not connected to the recognition service"); + return false; + } /** changes the listener */ private void handleChangeListener(RecognitionListener listener) { diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index c8396c4d4979..000e4ceded22 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -880,7 +880,7 @@ public abstract class Animation implements Cloneable { region.inset(-1.0f, -1.0f); if (mFillBefore) { final Transformation previousTransformation = mPreviousTransformation; - applyTransformation(0.0f, previousTransformation); + applyTransformation(mInterpolator.getInterpolation(0.0f), previousTransformation); } } diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java index 98b259451ae1..1546dcd763bd 100644 --- a/core/java/android/view/animation/AnimationSet.java +++ b/core/java/android/view/animation/AnimationSet.java @@ -282,7 +282,9 @@ public class AnimationSet extends Animation { final Animation a = animations.get(i); temp.clear(); - a.applyTransformation(0.0f, temp); + final Interpolator interpolator = a.mInterpolator; + a.applyTransformation(interpolator != null ? interpolator.getInterpolation(0.0f) + : 0.0f, temp); previousTransformation.compose(temp); } } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 66a76315af10..fd6af05bd1d6 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -32,7 +32,6 @@ import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; -import android.util.Log; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -307,6 +306,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * Handles one frame of a fling */ private FlingRunnable mFlingRunnable; + + /** + * Handles scrolling between positions within the list. + */ + private PositionScroller mPositionScroller; /** * The offset in pixels form the top of the AdapterView to the top @@ -1588,6 +1592,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // let the fling runnable report it's new state which // should be idle mFlingRunnable.endFling(); + if (mScrollY != 0) { + mScrollY = 0; + invalidate(); + } } // Always hide the type filter dismissPopup(); @@ -1935,9 +1943,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } else { int touchMode = mTouchMode; if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) { - mScrollY = 0; if (mFlingRunnable != null) { mFlingRunnable.endFling(); + + if (mScrollY != 0) { + mScrollY = 0; + invalidate(); + } } } } @@ -2052,9 +2064,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te if (motionView != null) { motionViewPrevTop = motionView.getTop(); } + // No need to do all this work if we're not going to move anyway + boolean atEdge = false; if (incrementalDeltaY != 0) { - trackMotionScroll(deltaY, incrementalDeltaY); + atEdge = trackMotionScroll(deltaY, incrementalDeltaY); } // Check to see if we have bumped into the scroll limit @@ -2064,7 +2078,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // supposed to be final int motionViewRealTop = motionView.getTop(); final int motionViewNewTop = mMotionViewNewTop; - if (motionViewRealTop != motionViewNewTop) { + if (atEdge) { // Apply overscroll mScrollY -= incrementalDeltaY - (motionViewRealTop - motionViewPrevTop); @@ -2440,7 +2454,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } } - + void startSpringback() { if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) { mTouchMode = TOUCH_MODE_OVERFLING; @@ -2448,19 +2462,33 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te post(this); } } - + void startOverfling(int initialVelocity) { mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 0, 0, 0, getHeight()); mTouchMode = TOUCH_MODE_OVERFLING; invalidate(); post(this); } - + + void startScroll(int distance, int duration) { + int initialY = distance < 0 ? Integer.MAX_VALUE : 0; + mLastFlingY = initialY; + mScroller.startScroll(0, initialY, 0, distance, duration); + mTouchMode = TOUCH_MODE_FLING; + post(this); + } + private void endFling() { mTouchMode = TOUCH_MODE_REST; + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); clearScrollingCache(); + removeCallbacks(this); + + if (mPositionScroller != null) { + removeCallbacks(mPositionScroller); + } } public void run() { @@ -2553,6 +2581,278 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + + + class PositionScroller implements Runnable { + private static final int SCROLL_DURATION = 400; + + private static final int MOVE_DOWN_POS = 1; + private static final int MOVE_UP_POS = 2; + private static final int MOVE_DOWN_BOUND = 3; + private static final int MOVE_UP_BOUND = 4; + + private int mMode; + private int mTargetPos; + private int mBoundPos; + private int mLastSeenPos; + private int mScrollDuration; + private int mExtraScroll; + + PositionScroller() { + mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); + } + + void start(int position) { + final int firstPos = mFirstPosition; + final int lastPos = firstPos + getChildCount() - 1; + + int viewTravelCount = 0; + if (position <= firstPos) { + viewTravelCount = firstPos - position + 1; + mMode = MOVE_UP_POS; + } else if (position >= lastPos) { + viewTravelCount = position - lastPos + 1; + mMode = MOVE_DOWN_POS; + } else { + // Already on screen, nothing to do + return; + } + + if (viewTravelCount > 0) { + mScrollDuration = SCROLL_DURATION / viewTravelCount; + } else { + mScrollDuration = SCROLL_DURATION; + } + mTargetPos = position; + mBoundPos = INVALID_POSITION; + mLastSeenPos = INVALID_POSITION; + + post(this); + } + + void start(int position, int boundPosition) { + if (boundPosition == INVALID_POSITION) { + start(position); + return; + } + + final int firstPos = mFirstPosition; + final int lastPos = firstPos + getChildCount() - 1; + + int viewTravelCount = 0; + if (position < firstPos) { + final int boundPosFromLast = lastPos - boundPosition; + if (boundPosFromLast < 1) { + // Moving would shift our bound position off the screen. Abort. + return; + } + + final int posTravel = firstPos - position + 1; + final int boundTravel = boundPosFromLast - 1; + if (boundTravel < posTravel) { + viewTravelCount = boundTravel; + mMode = MOVE_UP_BOUND; + } else { + viewTravelCount = posTravel; + mMode = MOVE_UP_POS; + } + } else if (position > lastPos) { + final int boundPosFromFirst = boundPosition - firstPos; + if (boundPosFromFirst < 1) { + // Moving would shift our bound position off the screen. Abort. + return; + } + + final int posTravel = position - lastPos + 1; + final int boundTravel = boundPosFromFirst - 1; + if (boundTravel < posTravel) { + viewTravelCount = boundTravel; + mMode = MOVE_DOWN_BOUND; + } else { + viewTravelCount = posTravel; + mMode = MOVE_DOWN_POS; + } + } else { + // Already on screen, nothing to do + return; + } + + if (viewTravelCount > 0) { + mScrollDuration = SCROLL_DURATION / viewTravelCount; + } else { + mScrollDuration = SCROLL_DURATION; + } + mTargetPos = position; + mBoundPos = boundPosition; + mLastSeenPos = INVALID_POSITION; + + post(this); + } + + void stop() { + removeCallbacks(this); + } + + public void run() { + final int listHeight = getHeight(); + final int firstPos = mFirstPosition; + + switch (mMode) { + case MOVE_DOWN_POS: { + final int lastViewIndex = getChildCount() - 1; + final int lastPos = firstPos + lastViewIndex; + + if (lastPos == mLastSeenPos) { + // No new views, let things keep going. + post(this); + return; + } + + final View lastView = getChildAt(lastViewIndex); + final int lastViewHeight = lastView.getHeight(); + final int lastViewTop = lastView.getTop(); + final int lastViewPixelsShowing = listHeight - lastViewTop; + + smoothScrollBy(lastViewHeight - lastViewPixelsShowing + mExtraScroll, + mScrollDuration); + + mLastSeenPos = lastPos; + if (lastPos != mTargetPos) { + post(this); + } + break; + } + + case MOVE_DOWN_BOUND: { + final int nextViewIndex = 1; + if (firstPos == mBoundPos || getChildCount() <= nextViewIndex) { + return; + } + final int nextPos = firstPos + nextViewIndex; + + if (nextPos == mLastSeenPos) { + // No new views, let things keep going. + post(this); + return; + } + + final View nextView = getChildAt(nextViewIndex); + final int nextViewHeight = nextView.getHeight(); + final int nextViewTop = nextView.getTop(); + final int extraScroll = mExtraScroll; + if (nextPos != mBoundPos) { + smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll), + mScrollDuration); + + mLastSeenPos = nextPos; + + post(this); + } else { + if (nextViewTop > extraScroll) { + smoothScrollBy(nextViewTop - extraScroll, mScrollDuration); + } + } + break; + } + + case MOVE_UP_POS: { + if (firstPos == mLastSeenPos) { + // No new views, let things keep going. + post(this); + return; + } + + final View firstView = getChildAt(0); + final int firstViewTop = firstView.getTop(); + + smoothScrollBy(firstViewTop - mExtraScroll, mScrollDuration); + + mLastSeenPos = firstPos; + + if (firstPos != mTargetPos) { + post(this); + } + break; + } + + case MOVE_UP_BOUND: { + final int lastViewIndex = getChildCount() - 2; + if (lastViewIndex < 0) { + return; + } + final int lastPos = firstPos + lastViewIndex; + + if (lastPos == mLastSeenPos) { + // No new views, let things keep going. + post(this); + return; + } + + final View lastView = getChildAt(lastViewIndex); + final int lastViewHeight = lastView.getHeight(); + final int lastViewTop = lastView.getTop(); + final int lastViewPixelsShowing = listHeight - lastViewTop; + mLastSeenPos = lastPos; + if (lastPos != mBoundPos) { + smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration); + post(this); + } else { + final int bottom = listHeight - mExtraScroll; + final int lastViewBottom = lastViewTop + lastViewHeight; + if (bottom > lastViewBottom) { + smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration); + } + } + break; + } + + default: + break; + } + } + } + + /** + * Smoothly scroll to the specified adapter position. The view will + * scroll such that the indicated position is displayed. + * @param position Scroll to this adapter position. + */ + public void smoothScrollToPosition(int position) { + if (mPositionScroller == null) { + mPositionScroller = new PositionScroller(); + } + mPositionScroller.start(position); + } + + /** + * Smoothly scroll to the specified adapter position. The view will + * scroll such that the indicated position is displayed, but it will + * stop early if scrolling further would scroll boundPosition out of + * view. + * @param position Scroll to this adapter position. + * @param boundPosition Do not scroll if it would move this adapter + * position out of view. + */ + public void smoothScrollToPosition(int position, int boundPosition) { + if (mPositionScroller == null) { + mPositionScroller = new PositionScroller(); + } + mPositionScroller.start(position, boundPosition); + } + + /** + * Smoothly scroll by distance pixels over duration milliseconds. + * @param distance Distance to scroll in pixels. + * @param duration Duration of the scroll animation in milliseconds. + */ + public void smoothScrollBy(int distance, int duration) { + if (mFlingRunnable == null) { + mFlingRunnable = new FlingRunnable(); + } else { + mFlingRunnable.endFling(); + } + mFlingRunnable.startScroll(distance, duration); + } private void createScrollingCache() { if (mScrollingCacheEnabled && !mCachingStarted) { @@ -2588,11 +2888,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion * began. Positive numbers mean the user's finger is moving down the screen. * @param incrementalDeltaY Change in deltaY from the previous event. + * @return true if we're already at the beginning/end of the list and have nothing to do. */ - void trackMotionScroll(int deltaY, int incrementalDeltaY) { + boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { final int childCount = getChildCount(); if (childCount == 0) { - return; + return true; } final int firstTop = getChildAt(0).getTop(); @@ -2618,98 +2919,99 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); } - final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); - - if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) { - hideSelector(); - offsetChildrenTopAndBottom(incrementalDeltaY); - if (!awakenScrollBars()) { - invalidate(); - } - mMotionViewNewTop = mMotionViewOriginalTop + deltaY; - } else { - final int firstPosition = mFirstPosition; + final int firstPosition = mFirstPosition; - if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) { - // Don't need to move views down if the top of the first position is already visible - return; - } + if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) { + // Don't need to move views down if the top of the first position + // is already visible + return true; + } - if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) { - // Don't need to move views up if the bottom of the last position is already visible - return; - } + if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) { + // Don't need to move views up if the bottom of the last position + // is already visible + return true; + } - final boolean down = incrementalDeltaY < 0; + final boolean down = incrementalDeltaY < 0; - hideSelector(); + hideSelector(); - final int headerViewsCount = getHeaderViewsCount(); - final int footerViewsStart = mItemCount - getFooterViewsCount(); + final int headerViewsCount = getHeaderViewsCount(); + final int footerViewsStart = mItemCount - getFooterViewsCount(); - int start = 0; - int count = 0; + int start = 0; + int count = 0; - if (down) { - final int top = listPadding.top - incrementalDeltaY; - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child.getBottom() >= top) { - break; - } else { - count++; - int position = firstPosition + i; - if (position >= headerViewsCount && position < footerViewsStart) { - mRecycler.addScrapView(child); - - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, - ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, - firstPosition + i, -1); - } + if (down) { + final int top = listPadding.top - incrementalDeltaY; + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child.getBottom() >= top) { + break; + } else { + count++; + int position = firstPosition + i; + if (position >= headerViewsCount && position < footerViewsStart) { + mRecycler.addScrapView(child); + + if (ViewDebug.TRACE_RECYCLER) { + ViewDebug.trace(child, + ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, + firstPosition + i, -1); } } } - } else { - final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY; - for (int i = childCount - 1; i >= 0; i--) { - final View child = getChildAt(i); - if (child.getTop() <= bottom) { - break; - } else { - start = i; - count++; - int position = firstPosition + i; - if (position >= headerViewsCount && position < footerViewsStart) { - mRecycler.addScrapView(child); - - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, - ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, - firstPosition + i, -1); - } + } + } else { + final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY; + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + if (child.getTop() <= bottom) { + break; + } else { + start = i; + count++; + int position = firstPosition + i; + if (position >= headerViewsCount && position < footerViewsStart) { + mRecycler.addScrapView(child); + + if (ViewDebug.TRACE_RECYCLER) { + ViewDebug.trace(child, + ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, + firstPosition + i, -1); } } } } + } + + mMotionViewNewTop = mMotionViewOriginalTop + deltaY; - mMotionViewNewTop = mMotionViewOriginalTop + deltaY; + mBlockLayoutRequests = true; - mBlockLayoutRequests = true; + if (count > 0) { detachViewsFromParent(start, count); - offsetChildrenTopAndBottom(incrementalDeltaY); + } + offsetChildrenTopAndBottom(incrementalDeltaY); - if (down) { - mFirstPosition += count; - } + if (down) { + mFirstPosition += count; + } - invalidate(); - fillGap(down); - mBlockLayoutRequests = false; + invalidate(); - invokeOnItemScrollListener(); - awakenScrollBars(); + final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); + if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { + fillGap(down); } + + mBlockLayoutRequests = false; + + invokeOnItemScrollListener(); + awakenScrollBars(); + + return false; } /** diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index 405461a12bda..a4b20da1e2d3 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -18,14 +18,12 @@ package android.widget; import com.android.internal.R; -import java.util.ArrayList; - import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -35,6 +33,8 @@ import android.view.View; import android.view.ContextMenu.ContextMenuInfo; import android.widget.ExpandableListConnector.PositionMetadata; +import java.util.ArrayList; + /** * A view that shows items in a vertically scrolling two-level list. This * differs from the {@link ListView} by allowing two levels: groups which can @@ -541,6 +541,12 @@ public class ExpandableListView extends ListView { if (mOnGroupExpandListener != null) { mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos); } + + final int groupPos = posMetadata.position.groupPos; + final int groupFlatPos = posMetadata.position.flatListPos; + + smoothScrollToPosition(groupFlatPos + mAdapter.getChildrenCount(groupPos), + groupFlatPos); } returnValue = true; diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index b4e2790ae56e..ea5841a01641 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -360,7 +360,8 @@ public class LinearLayout extends ViewGroup { // Optimization: don't bother measuring children who are going to use // leftover space. These views will get measured again down below if // there is any leftover space. - mTotalLength += lp.topMargin + lp.bottomMargin; + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin); } else { int oldHeight = Integer.MIN_VALUE; @@ -385,8 +386,9 @@ public class LinearLayout extends ViewGroup { } final int childHeight = child.getMeasuredHeight(); - mTotalLength += childHeight + lp.topMargin + - lp.bottomMargin + getNextLocationOffset(child); + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + + lp.bottomMargin + getNextLocationOffset(child)); if (useLargestChild) { largestChildHeight = Math.max(childHeight, largestChildHeight); @@ -459,8 +461,10 @@ public class LinearLayout extends ViewGroup { final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); - mTotalLength += largestChildHeight + lp.topMargin+ lp.bottomMargin + - getNextLocationOffset(child); + // Account for negative margins + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + largestChildHeight + + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); } } @@ -536,12 +540,14 @@ public class LinearLayout extends ViewGroup { allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT; - mTotalLength += child.getMeasuredHeight() + lp.topMargin + - lp.bottomMargin + getNextLocationOffset(child); + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() + + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); } // Add in our padding - mTotalLength += mPaddingTop + mPaddingBottom; + mTotalLength += mPaddingTop + mPaddingBottom; + // TODO: Should we recompute the heightSpec based on the new total length? } else { alternativeMaxWidth = Math.max(alternativeMaxWidth, weightedMaxWidth); @@ -651,7 +657,8 @@ public class LinearLayout extends ViewGroup { // Optimization: don't bother measuring children who are going to use // leftover space. These views will get measured again down below if // there is any leftover space. - mTotalLength += lp.leftMargin + lp.rightMargin; + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin); // Baseline alignment requires to measure widgets to obtain the // baseline offset (in particular for TextViews). @@ -686,8 +693,9 @@ public class LinearLayout extends ViewGroup { } final int childWidth = child.getMeasuredWidth(); - mTotalLength += childWidth + lp.leftMargin + lp.rightMargin + - getNextLocationOffset(child); + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + + lp.rightMargin + getNextLocationOffset(child)); if (useLargestChild) { largestChildWidth = Math.max(childWidth, largestChildWidth); @@ -772,8 +780,9 @@ public class LinearLayout extends ViewGroup { final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); - mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin + - getNextLocationOffset(child); + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + largestChildWidth + + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); } } @@ -843,8 +852,9 @@ public class LinearLayout extends ViewGroup { } } - mTotalLength += child.getMeasuredWidth() + lp.leftMargin + - lp.rightMargin + getNextLocationOffset(child); + final int totalLength = mTotalLength; + mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredWidth() + + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child)); boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT; @@ -875,6 +885,7 @@ public class LinearLayout extends ViewGroup { // Add in our padding mTotalLength += mPaddingLeft + mPaddingRight; + // TODO: Should we update widthSize with the new total length? // Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP, // the most common case diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 2d36bc82f68e..fd18db491b12 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -39,6 +39,7 @@ import com.android.internal.R; * A view for selecting a number * * For a dialog using this view, see {@link android.app.TimePickerDialog}. + * @hide */ @Widget public class NumberPicker extends LinearLayout { diff --git a/core/java/com/android/internal/widget/WeightedLinearLayout.java b/core/java/com/android/internal/widget/WeightedLinearLayout.java index b90204e7cd0d..3d09f08826f9 100644 --- a/core/java/com/android/internal/widget/WeightedLinearLayout.java +++ b/core/java/com/android/internal/widget/WeightedLinearLayout.java @@ -52,7 +52,8 @@ public class WeightedLinearLayout extends LinearLayout { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); - final boolean isPortrait = metrics.widthPixels < metrics.heightPixels; + final int screenWidth = metrics.widthPixels; + final boolean isPortrait = screenWidth < metrics.heightPixels; final int widthMode = getMode(widthMeasureSpec); @@ -62,14 +63,13 @@ public class WeightedLinearLayout extends LinearLayout { int height = getMeasuredHeight(); boolean measure = false; - final int widthSize = getSize(widthMeasureSpec); widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY); heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, EXACTLY); final float widthWeight = isPortrait ? mMinorWeight : mMajorWeight; if (widthMode == AT_MOST && widthWeight > 0.0f) { - if (width < (widthSize * widthWeight)) { - widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (widthSize * widthWeight), + if (width < (screenWidth * widthWeight)) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (screenWidth * widthWeight), EXACTLY); measure = true; } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 7fd58e89707d..1ffd265dd221 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -70,7 +70,6 @@ LOCAL_SRC_FILES:= \ android_util_Process.cpp \ android_util_StringBlock.cpp \ android_util_XmlBlock.cpp \ - android_util_Base64.cpp \ android/graphics/Bitmap.cpp \ android/graphics/BitmapFactory.cpp \ android/graphics/Camera.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 1d22de340376..7c8df0366748 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -153,7 +153,6 @@ extern int register_android_server_BluetoothEventLoop(JNIEnv *env); extern int register_android_server_BluetoothA2dpService(JNIEnv* env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env); -extern int register_android_util_Base64(JNIEnv* env); extern int register_android_location_GpsLocationProvider(JNIEnv* env); extern int register_android_backup_BackupDataInput(JNIEnv *env); extern int register_android_backup_BackupDataOutput(JNIEnv *env); @@ -1266,7 +1265,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_server_BluetoothA2dpService), REG_JNI(register_android_message_digest_sha1), REG_JNI(register_android_ddm_DdmHandleNativeHeap), - REG_JNI(register_android_util_Base64), REG_JNI(register_android_location_GpsLocationProvider), REG_JNI(register_android_backup_BackupDataInput), REG_JNI(register_android_backup_BackupDataOutput), diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp index 020aff4a5c3b..c19701073e82 100644 --- a/core/jni/android_database_SQLiteDatabase.cpp +++ b/core/jni/android_database_SQLiteDatabase.cpp @@ -143,12 +143,68 @@ done: if (handle != NULL) sqlite3_close(handle); } +void sqlTrace(void *databaseName, const char *sql) { + LOGI("sql_statement|%s|%s\n", (char *)databaseName, sql); +} + +/* public native void enableSqlTracing(); */ +static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName) +{ + sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); + char const *path = env->GetStringUTFChars(databaseName, NULL); + if (path == NULL) { + LOGE("Failure in enableSqlTracing(). VM ran out of memory?\n"); + return; // VM would have thrown OutOfMemoryError + } + int len = strlen(path); + char *traceFuncArg = (char *)malloc(len + 1); + strncpy(traceFuncArg, path, len); + traceFuncArg[len-1] = NULL; + env->ReleaseStringUTFChars(databaseName, path); + sqlite3_trace(handle, &sqlTrace, (void *)traceFuncArg); + LOGI("will be printing all sql statements executed on database = %s\n", traceFuncArg); +} + +void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) { + double d = tm/1000000.0; + LOGI("elapsedTime4Sql|%s|%.3f ms|%s\n", (char *)databaseName, d, sql); +} + +/* public native void enableSqlProfiling(); */ +static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName) +{ + sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); + char const *path = env->GetStringUTFChars(databaseName, NULL); + if (path == NULL) { + LOGE("Failure in enableSqlProfiling(). VM ran out of memory?\n"); + return; // VM would have thrown OutOfMemoryError + } + int len = strlen(path); + char *traceFuncArg = (char *)malloc(len + 1); + strncpy(traceFuncArg, path, len); + traceFuncArg[len-1] = NULL; + env->ReleaseStringUTFChars(databaseName, path); + sqlite3_profile(handle, &sqlProfile, (void *)traceFuncArg); + LOGI("will be printing execution time of all sql statements executed on database = %s\n", + traceFuncArg); +} + /* public native void close(); */ static void dbclose(JNIEnv* env, jobject object) { sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle); if (handle != NULL) { + // release the memory associated with the traceFuncArg in enableSqlTracing function + void *traceFuncArg = sqlite3_trace(handle, &sqlTrace, NULL); + if (traceFuncArg != NULL) { + free(traceFuncArg); + } + // release the memory associated with the traceFuncArg in enableSqlProfiling function + traceFuncArg = sqlite3_profile(handle, &sqlProfile, NULL); + if (traceFuncArg != NULL) { + free(traceFuncArg); + } LOGV("Closing database: handle=%p\n", handle); int result = sqlite3_close(handle); if (result == SQLITE_OK) { @@ -357,6 +413,8 @@ static JNINativeMethod sMethods[] = /* name, signature, funcPtr */ {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen}, {"dbclose", "()V", (void *)dbclose}, + {"enableSqlTracing", "(Ljava/lang/String;)V", (void *)enableSqlTracing}, + {"enableSqlProfiling", "(Ljava/lang/String;)V", (void *)enableSqlProfiling}, {"native_execSQL", "(Ljava/lang/String;)V", (void *)native_execSQL}, {"lastInsertRow", "()J", (void *)lastInsertRow}, {"lastChangeCount", "()I", (void *)lastChangeCount}, diff --git a/core/jni/android_util_Base64.cpp b/core/jni/android_util_Base64.cpp deleted file mode 100644 index bc69747c1bcf..000000000000 --- a/core/jni/android_util_Base64.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* //device/libs/android_runtime/android_util_Base64.cpp -** -** Copyright 2008, 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. -*/ - -/********************************************************* -* -* This code was copied from -* system/extra/ssh/dropbear-0.49/libtomcrypt/src/misc/base64/base64_decode.c -* -*********************************************************/ - -#define LOG_TAG "Base64" - -#include <utils/Log.h> - -#include <android_runtime/AndroidRuntime.h> - -#include "JNIHelp.h" - -#include <sys/errno.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <fcntl.h> -#include <signal.h> - -namespace android { - -static const unsigned char map[256] = { -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, -255, 254, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, - 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, -255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, - 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -255, 255, 255, 255 }; - -/** - base64 decode a block of memory - @param in The base64 data to decode - @param inlen The length of the base64 data - @param out [out] The destination of the binary decoded data - @param outlen [in/out] The max size and resulting size of the decoded data - @return 0 if successful -*/ -int base64_decode(const unsigned char *in, unsigned long inlen, - unsigned char *out, unsigned long *outlen) -{ - unsigned long t, x, y, z; - unsigned char c; - int g; - - g = 3; - for (x = y = z = t = 0; x < inlen; x++) { - c = map[in[x]&0xFF]; - if (c == 255) continue; - /* the final = symbols are read and used to trim the remaining bytes */ - if (c == 254) { - c = 0; - /* prevent g < 0 which would potentially allow an overflow later */ - if (--g < 0) { - return -3; - } - } else if (g != 3) { - /* we only allow = to be at the end */ - return -4; - } - - t = (t<<6)|c; - - if (++y == 4) { - if (z + g > *outlen) { - return -2; - } - out[z++] = (unsigned char)((t>>16)&255); - if (g > 1) out[z++] = (unsigned char)((t>>8)&255); - if (g > 2) out[z++] = (unsigned char)(t&255); - y = t = 0; - } - } - if (y != 0) { - return -5; - } - *outlen = z; - return 0; -} - -static jbyteArray decodeBase64(JNIEnv *env, jobject jobj, jstring jdata) -{ - const char * rawData = env->GetStringUTFChars(jdata, NULL); - int stringLength = env->GetStringUTFLength(jdata); - - int resultLength = stringLength / 4 * 3; - if (rawData[stringLength-1] == '=') { - resultLength -= 1; - if (rawData[stringLength-2] == '=') { - resultLength -= 1; - } - } - - jbyteArray byteArray = env->NewByteArray(resultLength); - jbyte* byteArrayData = env->GetByteArrayElements(byteArray, NULL); - - unsigned long outlen = resultLength; - int result = base64_decode((const unsigned char*)rawData, stringLength, (unsigned char *)byteArrayData, &outlen); - if (result != 0) - memset((unsigned char *)byteArrayData, -result, resultLength); - - env->ReleaseStringUTFChars(jdata, rawData); - env->ReleaseByteArrayElements(byteArray, byteArrayData, 0); - - return byteArray; -} - -static const JNINativeMethod methods[] = { - {"decodeBase64Native", "(Ljava/lang/String;)[B", (void*)decodeBase64 } -}; - -static const char* const kBase64PathName = "android/os/Base64Utils"; - -int register_android_util_Base64(JNIEnv* env) -{ - jclass clazz; - - clazz = env->FindClass(kBase64PathName); - LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Base64Utils"); - - return AndroidRuntime::registerNativeMethods( - env, kBase64PathName, - methods, NELEM(methods)); -} - -} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 1406b66459b6..713e725878e0 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -679,6 +679,12 @@ android:label="@string/permlab_setWallpaperHints" android:description="@string/permdesc_setWallpaperHints" /> + <!-- Allows applications to set the system time --> + <permission android:name="android.permission.SET_TIME" + android:protectionLevel="signatureOrSystem" + android:label="@string/permlab_setTime" + android:description="@string/permdesc_setTime" /> + <!-- Allows applications to set the system time zone --> <permission android:name="android.permission.SET_TIME_ZONE" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" diff --git a/core/res/res/drawable-hdpi/stat_sys_secure.png b/core/res/res/drawable-hdpi/stat_sys_secure.png Binary files differnew file mode 100644 index 000000000000..4bae258ae823 --- /dev/null +++ b/core/res/res/drawable-hdpi/stat_sys_secure.png diff --git a/core/res/res/drawable-mdpi/stat_sys_secure.png b/core/res/res/drawable-mdpi/stat_sys_secure.png Binary files differnew file mode 100644 index 000000000000..5f9ae69cb215 --- /dev/null +++ b/core/res/res/drawable-mdpi/stat_sys_secure.png diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml index 231e11c2c327..7ae68f900b45 100644 --- a/core/res/res/layout/alert_dialog.xml +++ b/core/res/res/layout/alert_dialog.xml @@ -28,8 +28,8 @@ android:paddingBottom="3dip" android:paddingLeft="3dip" android:paddingRight="1dip" - android:majorWeight="0.5" - android:minorWeight="0.8"> + android:majorWeight="0.65" + android:minorWeight="0.9"> <LinearLayout android:id="@+id/topPanel" android:layout_width="match_parent" diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 3dbfa259f2d8..cdc15c29ccc0 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -107,6 +107,7 @@ icons in the status bar that are not notifications. --> <string-array name="status_bar_icon_order"> <item><xliff:g id="id">clock</xliff:g></item> + <item><xliff:g id="id">secure</xliff:g></item> <item><xliff:g id="id">alarm_clock</xliff:g></item> <item><xliff:g id="id">battery</xliff:g></item> <item><xliff:g id="id">phone_signal</xliff:g></item> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d1bfc683001b..4df570c30066 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1017,6 +1017,12 @@ configuration, and installed applications.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_setTime">set time</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_setTime">Allows an application to change + the phone\'s clock time.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_setTimeZone">set time zone</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_setTimeZone">Allows an application to change diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java index 533338e027e2..1505a7c0ff3a 100644 --- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java +++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java @@ -18,16 +18,23 @@ package android.content; import android.test.AndroidTestCase; import android.test.RenamingDelegatingContext; +import android.test.suitebuilder.annotation.SmallTest; import android.test.mock.MockContext; import android.test.mock.MockContentResolver; import android.accounts.Account; +import android.os.Bundle; + +import java.util.List; +import java.io.File; public class SyncStorageEngineTest extends AndroidTestCase { /** * Test that we handle the case of a history row being old enough to purge before the * correcponding sync is finished. This can happen if the clock changes while we are syncing. + * */ + @SmallTest public void testPurgeActiveSync() throws Exception { final Account account = new Account("a@example.com", "example.type"); final String authority = "testprovider"; @@ -41,7 +48,150 @@ public class SyncStorageEngineTest extends AndroidTestCase { long historyId = engine.insertStartSyncEvent( account, authority, time0, SyncStorageEngine.SOURCE_LOCAL); long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2; - engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0); + engine.stopSyncEvent(historyId, new Bundle(), time1 - time0, "yay", 0, 0); + } + + /** + * Test that we can create, remove and retrieve periodic syncs + */ + @SmallTest + public void testPeriodics() throws Exception { + final Account account1 = new Account("a@example.com", "example.type"); + final Account account2 = new Account("b@example.com", "example.type.2"); + final String authority = "testprovider"; + final Bundle extras1 = new Bundle(); + extras1.putString("a", "1"); + final Bundle extras2 = new Bundle(); + extras2.putString("a", "2"); + final int period1 = 200; + final int period2 = 1000; + + PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1); + PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1); + PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2); + PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2); + + MockContentResolver mockResolver = new MockContentResolver(); + + SyncStorageEngine engine = SyncStorageEngine.newTestInstance( + new TestContext(mockResolver, getContext())); + + removePeriodicSyncs(engine, account1, authority); + removePeriodicSyncs(engine, account2, authority); + + // this should add two distinct periodic syncs for account1 and one for account2 + engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period); + engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period); + engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period); + engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period); + + List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority); + + assertEquals(2, syncs.size()); + + assertEquals(sync1, syncs.get(0)); + assertEquals(sync3, syncs.get(1)); + + engine.removePeriodicSync(sync1.account, sync1.authority, sync1.extras); + + syncs = engine.getPeriodicSyncs(account1, authority); + assertEquals(1, syncs.size()); + assertEquals(sync3, syncs.get(0)); + + syncs = engine.getPeriodicSyncs(account2, authority); + assertEquals(1, syncs.size()); + assertEquals(sync4, syncs.get(0)); + } + + private void removePeriodicSyncs(SyncStorageEngine engine, Account account, String authority) { + engine.setIsSyncable(account, authority, engine.getIsSyncable(account, authority)); + List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority); + for (PeriodicSync sync : syncs) { + engine.removePeriodicSync(sync.account, sync.authority, sync.extras); + } + } + + @SmallTest + public void testAuthorityPersistence() throws Exception { + final Account account1 = new Account("a@example.com", "example.type"); + final Account account2 = new Account("b@example.com", "example.type.2"); + final String authority1 = "testprovider1"; + final String authority2 = "testprovider2"; + final Bundle extras1 = new Bundle(); + extras1.putString("a", "1"); + final Bundle extras2 = new Bundle(); + extras2.putString("a", "2"); + extras2.putLong("b", 2); + extras2.putInt("c", 1); + extras2.putBoolean("d", true); + extras2.putDouble("e", 1.2); + extras2.putFloat("f", 4.5f); + extras2.putParcelable("g", account1); + final int period1 = 200; + final int period2 = 1000; + + PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1); + PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1); + PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1); + PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2); + PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1); + + MockContentResolver mockResolver = new MockContentResolver(); + + SyncStorageEngine engine = SyncStorageEngine.newTestInstance( + new TestContext(mockResolver, getContext())); + + removePeriodicSyncs(engine, account1, authority1); + removePeriodicSyncs(engine, account2, authority1); + removePeriodicSyncs(engine, account1, authority2); + removePeriodicSyncs(engine, account2, authority2); + + engine.setMasterSyncAutomatically(false); + + engine.setIsSyncable(account1, authority1, 1); + engine.setSyncAutomatically(account1, authority1, true); + + engine.setIsSyncable(account2, authority1, 1); + engine.setSyncAutomatically(account2, authority1, true); + + engine.setIsSyncable(account1, authority2, 1); + engine.setSyncAutomatically(account1, authority2, false); + + engine.setIsSyncable(account2, authority2, 0); + engine.setSyncAutomatically(account2, authority2, true); + + engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period); + engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period); + engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period); + engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period); + engine.addPeriodicSync(sync5.account, sync5.authority, sync5.extras, sync5.period); + + engine.writeAllState(); + engine.clearAndReadState(); + + List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority1); + assertEquals(2, syncs.size()); + assertEquals(sync1, syncs.get(0)); + assertEquals(sync2, syncs.get(1)); + + syncs = engine.getPeriodicSyncs(account1, authority2); + assertEquals(2, syncs.size()); + assertEquals(sync3, syncs.get(0)); + assertEquals(sync4, syncs.get(1)); + + syncs = engine.getPeriodicSyncs(account2, authority1); + assertEquals(1, syncs.size()); + assertEquals(sync5, syncs.get(0)); + + assertEquals(true, engine.getSyncAutomatically(account1, authority1)); + assertEquals(true, engine.getSyncAutomatically(account2, authority1)); + assertEquals(false, engine.getSyncAutomatically(account1, authority2)); + assertEquals(true, engine.getSyncAutomatically(account2, authority2)); + + assertEquals(1, engine.getIsSyncable(account1, authority1)); + assertEquals(1, engine.getIsSyncable(account2, authority1)); + assertEquals(1, engine.getIsSyncable(account1, authority2)); + assertEquals(0, engine.getIsSyncable(account2, authority2)); } } @@ -49,15 +199,26 @@ class TestContext extends ContextWrapper { ContentResolver mResolver; + private final Context mRealContext; + public TestContext(ContentResolver resolver, Context realContext) { super(new RenamingDelegatingContext(new MockContext(), realContext, "test.")); + mRealContext = realContext; mResolver = resolver; } @Override + public File getFilesDir() { + return mRealContext.getFilesDir(); + } + + @Override public void enforceCallingOrSelfPermission(String permission, String message) { } + @Override + public void sendBroadcast(Intent intent) { + } @Override public ContentResolver getContentResolver() { diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDebugTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDebugTest.java deleted file mode 100644 index ea807bd64cb5..000000000000 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteDebugTest.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.database.sqlite; - -import junit.framework.TestCase; - -/** - * Tests for the SQLiteDebug - */ -public class SQLiteDebugTest extends TestCase { - private static final String TEST_DB = "test.db"; - - public void testCaptureSql() { - String rslt = SQLiteDebug.captureSql(TEST_DB, "select * from t1 where a=? and b=1", - new Object[] {"blah"}); - String expectedVal = "select * from t1 where a='blah' and b=1"; - assertTrue(rslt.equals("captured_sql|" + TEST_DB + "|" + expectedVal)); - - rslt = SQLiteDebug.captureSql(TEST_DB, "select * from t1 where a=?", - new Object[] {"blah"}); - expectedVal = "select * from t1 where a='blah'"; - assertTrue(rslt.equals("captured_sql|" + TEST_DB + "|" + expectedVal)); - - rslt = SQLiteDebug.captureSql(TEST_DB, "select * from t1 where a=1", - new Object[] {"blah"}); - assertTrue(rslt.startsWith("too many bindArgs provided.")); - - rslt = SQLiteDebug.captureSql(TEST_DB, "update t1 set a=? where b=?", - new Object[] {"blah", "foo"}); - expectedVal = "update t1 set a='blah' where b='foo'"; - assertTrue(rslt.equals("captured_sql|" + TEST_DB + "|" + expectedVal)); - } -} diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index 0a2fe4c633a7..6041b837730e 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -28,16 +28,16 @@ import java.util.ArrayList; * {@hide} */ public class KeyStore { - public static int NO_ERROR = 1; - public static int LOCKED = 2; - public static int UNINITIALIZED = 3; - public static int SYSTEM_ERROR = 4; - public static int PROTOCOL_ERROR = 5; - public static int PERMISSION_DENIED = 6; - public static int KEY_NOT_FOUND = 7; - public static int VALUE_CORRUPTED = 8; - public static int UNDEFINED_ACTION = 9; - public static int WRONG_PASSWORD = 10; + public static final int NO_ERROR = 1; + public static final int LOCKED = 2; + public static final int UNINITIALIZED = 3; + public static final int SYSTEM_ERROR = 4; + public static final int PROTOCOL_ERROR = 5; + public static final int PERMISSION_DENIED = 6; + public static final int KEY_NOT_FOUND = 7; + public static final int VALUE_CORRUPTED = 8; + public static final int UNDEFINED_ACTION = 9; + public static final int WRONG_PASSWORD = 10; private static final LocalSocketAddress sAddress = new LocalSocketAddress( "keystore", LocalSocketAddress.Namespace.RESERVED); @@ -56,8 +56,8 @@ public class KeyStore { } public byte[] get(byte[] key) { - byte[][] values = execute('g', key); - return (values == null) ? null : values[0]; + ArrayList<byte[]> values = execute('g', key); + return (values == null || values.size() == 0) ? null : values.get(0); } public String get(String key) { @@ -93,7 +93,8 @@ public class KeyStore { } public byte[][] saw(byte[] prefix) { - return execute('s', prefix); + ArrayList<byte[]> values = execute('s', prefix); + return (values == null) ? null : values.toArray(new byte[values.size()][]); } public String[] saw(String prefix) { @@ -148,7 +149,7 @@ public class KeyStore { return mError; } - private byte[][] execute(int code, byte[]... parameters) { + private ArrayList<byte[]> execute(int code, byte[]... parameters) { mError = PROTOCOL_ERROR; for (byte[] parameter : parameters) { @@ -179,7 +180,7 @@ public class KeyStore { return null; } - ArrayList<byte[]> results = new ArrayList<byte[]>(); + ArrayList<byte[]> values = new ArrayList<byte[]>(); while (true) { int i, j; if ((i = in.read()) == -1) { @@ -188,16 +189,16 @@ public class KeyStore { if ((j = in.read()) == -1) { return null; } - byte[] result = new byte[i << 8 | j]; - for (i = 0; i < result.length; i += j) { - if ((j = in.read(result, i, result.length - i)) == -1) { + byte[] value = new byte[i << 8 | j]; + for (i = 0; i < value.length; i += j) { + if ((j = in.read(value, i, value.length - i)) == -1) { return null; } } - results.add(result); + values.add(value); } mError = NO_ERROR; - return results.toArray(new byte[results.size()][]); + return values; } catch (IOException e) { // ignore } finally { diff --git a/libs/surfaceflinger/LayerBuffer.cpp b/libs/surfaceflinger/LayerBuffer.cpp index 2735aa2a31e2..bd3113b4da02 100644 --- a/libs/surfaceflinger/LayerBuffer.cpp +++ b/libs/surfaceflinger/LayerBuffer.cpp @@ -328,7 +328,8 @@ bool LayerBuffer::Source::transformed() const { LayerBuffer::BufferSource::BufferSource(LayerBuffer& layer, const ISurface::BufferHeap& buffers) - : Source(layer), mStatus(NO_ERROR), mBufferSize(0) + : Source(layer), mStatus(NO_ERROR), mBufferSize(0), + mUseEGLImageDirectly(true) { if (buffers.heap == NULL) { // this is allowed, but in this case, it is illegal to receive @@ -466,25 +467,38 @@ void LayerBuffer::BufferSource::onDraw(const Region& clip) const #if defined(EGL_ANDROID_image_native_buffer) if (mLayer.mFlags & DisplayHardware::DIRECT_TEXTURE) { - copybit_device_t* copybit = mLayer.mBlitEngine; - if (copybit && ourBuffer->supportsCopybit()) { - // create our EGLImageKHR the first time - err = initTempBuffer(); - if (err == NO_ERROR) { + err = INVALID_OPERATION; + if (ourBuffer->supportsCopybit()) { + // First, try to use the buffer as an EGLImage directly + if (mUseEGLImageDirectly) { // NOTE: Assume the buffer is allocated with the proper USAGE flags - const NativeBuffer& dst(mTempBuffer); - region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b))); - copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0); - copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF); - copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE); - err = copybit->stretch(copybit, &dst.img, &src.img, - &dst.crop, &src.crop, &clip); + sp<GraphicBuffer> buffer = new GraphicBuffer( + src.img.w, src.img.h, src.img.format, + GraphicBuffer::USAGE_HW_TEXTURE, + src.img.w, src.img.handle, false); + err = mLayer.initializeEglImage(buffer, &mTexture); if (err != NO_ERROR) { - clearTempBufferImage(); + mUseEGLImageDirectly = false; + } + } + copybit_device_t* copybit = mLayer.mBlitEngine; + if (copybit && err != NO_ERROR) { + // create our EGLImageKHR the first time + err = initTempBuffer(); + if (err == NO_ERROR) { + // NOTE: Assume the buffer is allocated with the proper USAGE flags + const NativeBuffer& dst(mTempBuffer); + region_iterator clip(Region(Rect(dst.crop.r, dst.crop.b))); + copybit->set_parameter(copybit, COPYBIT_TRANSFORM, 0); + copybit->set_parameter(copybit, COPYBIT_PLANE_ALPHA, 0xFF); + copybit->set_parameter(copybit, COPYBIT_DITHER, COPYBIT_ENABLE); + err = copybit->stretch(copybit, &dst.img, &src.img, + &dst.crop, &src.crop, &clip); + if (err != NO_ERROR) { + clearTempBufferImage(); + } } } - } else { - err = INVALID_OPERATION; } } #endif diff --git a/libs/surfaceflinger/LayerBuffer.h b/libs/surfaceflinger/LayerBuffer.h index e03f92c7b1a4..3257b76c45e7 100644 --- a/libs/surfaceflinger/LayerBuffer.h +++ b/libs/surfaceflinger/LayerBuffer.h @@ -145,6 +145,7 @@ private: mutable LayerBase::Texture mTexture; mutable NativeBuffer mTempBuffer; mutable sp<GraphicBuffer> mTempGraphicBuffer; + mutable bool mUseEGLImageDirectly; }; class OverlaySource : public Source { diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index e7351dcbf926..492692047384 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -229,6 +229,8 @@ void AudioPlayer::fillBuffer(void *data, size_t size) { mInputBuffer->release(); mInputBuffer = NULL; } + + mSeeking = false; } } @@ -240,8 +242,6 @@ void AudioPlayer::fillBuffer(void *data, size_t size) { Mutex::Autolock autoLock(mLock); - mSeeking = false; - if (err != OK) { mReachedEOS = true; memset((char *)data + size_done, 0, size_remaining); diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp index 51fcaf5f4438..c05d90a48248 100644 --- a/media/libstagefright/omx/tests/OMXHarness.cpp +++ b/media/libstagefright/omx/tests/OMXHarness.cpp @@ -512,7 +512,9 @@ static sp<MediaSource> CreateSourceForMime(const char *mime) { sp<MediaExtractor> extractor = CreateExtractorFromURI(url); - CHECK(extractor != NULL); + if (extractor == NULL) { + return NULL; + } for (size_t i = 0; i < extractor->countTracks(); ++i) { sp<MetaData> meta = extractor->getTrackMetaData(i); @@ -571,6 +573,10 @@ status_t Harness::testSeek( sp<MediaSource> source = CreateSourceForMime(mime); sp<MediaSource> seekSource = CreateSourceForMime(mime); + if (source == NULL || seekSource == NULL) { + return UNKNOWN_ERROR; + } + CHECK_EQ(seekSource->start(), OK); sp<MediaSource> codec = OMXCodec::Create( diff --git a/opengl/libagl/egl.cpp b/opengl/libagl/egl.cpp index 81864bd791bc..b6e0aae74ee8 100644 --- a/opengl/libagl/egl.cpp +++ b/opengl/libagl/egl.cpp @@ -2092,7 +2092,20 @@ EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, if (native_buffer->common.version != sizeof(android_native_buffer_t)) return setError(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR); - + + switch (native_buffer->format) { + case HAL_PIXEL_FORMAT_RGBA_8888: + case HAL_PIXEL_FORMAT_RGBX_8888: + case HAL_PIXEL_FORMAT_RGB_888: + case HAL_PIXEL_FORMAT_RGB_565: + case HAL_PIXEL_FORMAT_BGRA_8888: + case HAL_PIXEL_FORMAT_RGBA_5551: + case HAL_PIXEL_FORMAT_RGBA_4444: + break; + default: + return setError(EGL_BAD_PARAMETER, EGL_NO_IMAGE_KHR); + } + native_buffer->common.incRef(&native_buffer->common); return (EGLImageKHR)native_buffer; } diff --git a/opengl/libagl/texture.cpp b/opengl/libagl/texture.cpp index a1a776f99029..fa25fa92f0ca 100644 --- a/opengl/libagl/texture.cpp +++ b/opengl/libagl/texture.cpp @@ -1628,6 +1628,11 @@ void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) return; } + if (image == EGL_NO_IMAGE_KHR) { + ogles_error(c, GL_INVALID_VALUE); + return; + } + android_native_buffer_t* native_buffer = (android_native_buffer_t*)image; if (native_buffer->common.magic != ANDROID_NATIVE_BUFFER_MAGIC) { ogles_error(c, GL_INVALID_VALUE); @@ -1652,4 +1657,26 @@ void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) void glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image) { + ogles_context_t* c = ogles_context_t::get(); + if (target != GL_RENDERBUFFER_OES) { + ogles_error(c, GL_INVALID_ENUM); + return; + } + + if (image == EGL_NO_IMAGE_KHR) { + ogles_error(c, GL_INVALID_VALUE); + return; + } + + android_native_buffer_t* native_buffer = (android_native_buffer_t*)image; + if (native_buffer->common.magic != ANDROID_NATIVE_BUFFER_MAGIC) { + ogles_error(c, GL_INVALID_VALUE); + return; + } + if (native_buffer->common.version != sizeof(android_native_buffer_t)) { + ogles_error(c, GL_INVALID_VALUE); + return; + } + + // well, we're not supporting this extension anyways } diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp index d2f8ced55cb0..145e25eaadc9 100644 --- a/opengl/libs/EGL/egl.cpp +++ b/opengl/libs/EGL/egl.cpp @@ -166,7 +166,8 @@ struct egl_display_t { uint32_t magic; DisplayImpl disp[IMPL_NUM_IMPLEMENTATIONS]; EGLint numTotalConfigs; - volatile int32_t refs; + uint32_t refs; + Mutex lock; egl_display_t() : magic('_dpy'), numTotalConfigs(0) { } ~egl_display_t() { magic = 0; } @@ -644,7 +645,9 @@ EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor) egl_display_t * const dp = get_display(dpy); if (!dp) return setError(EGL_BAD_DISPLAY, EGL_FALSE); - if (android_atomic_inc(&dp->refs) > 0) { + Mutex::Autolock _l(dp->lock); + + if (dp->refs > 0) { if (major != NULL) *major = VERSION_MAJOR; if (minor != NULL) *minor = VERSION_MINOR; return EGL_TRUE; @@ -728,6 +731,7 @@ EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor) } if (res == EGL_TRUE) { + dp->refs++; if (major != NULL) *major = VERSION_MAJOR; if (minor != NULL) *minor = VERSION_MINOR; return EGL_TRUE; @@ -743,7 +747,15 @@ EGLBoolean eglTerminate(EGLDisplay dpy) egl_display_t* const dp = get_display(dpy); if (!dp) return setError(EGL_BAD_DISPLAY, EGL_FALSE); - if (android_atomic_dec(&dp->refs) != 1) + + Mutex::Autolock _l(dp->lock); + + if (dp->refs == 0) { + return setError(EGL_NOT_INITIALIZED, EGL_FALSE); + } + + // this is specific to Android, display termination is ref-counted. + if (dp->refs > 1) return EGL_TRUE; EGLBoolean res = EGL_FALSE; @@ -767,6 +779,7 @@ EGLBoolean eglTerminate(EGLDisplay dpy) // TODO: all egl_object_t should be marked for termination + dp->refs--; dp->numTotalConfigs = 0; clearTLS(); return res; diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java index 1efa5a3e5179..b7eea2e3d5bc 100755 --- a/packages/TtsService/src/android/tts/TtsService.java +++ b/packages/TtsService/src/android/tts/TtsService.java @@ -802,8 +802,8 @@ public class TtsService extends Service implements OnCompletionListener { } if (synthAvailable) { synthesizerLock.unlock(); + processSpeechQueue(); } - processSpeechQueue(); } } } @@ -882,8 +882,8 @@ public class TtsService extends Service implements OnCompletionListener { } if (synthAvailable) { synthesizerLock.unlock(); + processSpeechQueue(); } - processSpeechQueue(); } } } diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index f3306513d815..c28cf440a4cd 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -242,6 +242,14 @@ class AlarmManagerService extends IAlarmManager.Stub { setRepeating(type, bucketTime, interval, operation); } + public void setTime(long millis) { + mContext.enforceCallingOrSelfPermission( + "android.permission.SET_TIME", + "setTime"); + + SystemClock.setCurrentTimeMillis(millis); + } + public void setTimeZone(String tz) { mContext.enforceCallingOrSelfPermission( "android.permission.SET_TIME_ZONE", diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index 6795bdd20b0d..2f845e15e849 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -258,8 +258,11 @@ class BackupManagerService extends IBackupManager.Stub { // snapshot the pending-backup set and work on that ArrayList<BackupRequest> queue = new ArrayList<BackupRequest>(); + File oldJournal = mJournal; synchronized (mQueueLock) { - // Do we have any work to do? + // Do we have any work to do? Construct the work queue + // then release the synchronization lock to actually run + // the backup. if (mPendingBackups.size() > 0) { for (BackupRequest b: mPendingBackups.values()) { queue.add(b); @@ -268,20 +271,22 @@ class BackupManagerService extends IBackupManager.Stub { mPendingBackups.clear(); // Start a new backup-queue journal file too - File oldJournal = mJournal; mJournal = null; - // At this point, we have started a new journal file, and the old - // file identity is being passed to the backup processing thread. - // When it completes successfully, that old journal file will be - // deleted. If we crash prior to that, the old journal is parsed - // at next boot and the journaled requests fulfilled. - (new PerformBackupTask(transport, queue, oldJournal)).run(); - } else { - Log.v(TAG, "Backup requested but nothing pending"); - mWakelock.release(); } } + + if (queue.size() > 0) { + // At this point, we have started a new journal file, and the old + // file identity is being passed to the backup processing thread. + // When it completes successfully, that old journal file will be + // deleted. If we crash prior to that, the old journal is parsed + // at next boot and the journaled requests fulfilled. + (new PerformBackupTask(transport, queue, oldJournal)).run(); + } else { + Log.v(TAG, "Backup requested but nothing pending"); + mWakelock.release(); + } break; } diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index e14a9734c8c9..1e7dd992c4a0 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -212,11 +212,13 @@ class PowerManagerService extends IPowerManager.Stub private Sensor mLightSensor; private boolean mLightSensorEnabled; private float mLightSensorValue = -1; + private int mHighestLightSensorValue = -1; private float mLightSensorPendingValue = -1; private int mLightSensorScreenBrightness = -1; private int mLightSensorButtonBrightness = -1; private int mLightSensorKeyboardBrightness = -1; private boolean mDimScreen = true; + private boolean mIsDocked = false; private long mNextTimeout; private volatile int mPokey = 0; private volatile boolean mPokeAwakeOnSet = false; @@ -363,6 +365,15 @@ class PowerManagerService extends IPowerManager.Stub } } + private final class DockReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + dockStateChanged(state); + } + } + /** * Set the setting that determines whether the device stays on when plugged in. * The argument is a bit string, with each bit specifying a power source that, @@ -527,6 +538,9 @@ class PowerManagerService extends IPowerManager.Stub filter = new IntentFilter(); filter.addAction(Intent.ACTION_BOOT_COMPLETED); mContext.registerReceiver(new BootCompletedReceiver(), filter); + filter = new IntentFilter(); + filter.addAction(Intent.ACTION_DOCK_EVENT); + mContext.registerReceiver(new DockReceiver(), filter); // Listen for secure settings changes mContext.getContentResolver().registerContentObserver( @@ -1389,6 +1403,8 @@ class PowerManagerService extends IPowerManager.Stub // clear current value so we will update based on the new conditions // when the sensor is reenabled. mLightSensorValue = -1; + // reset our highest light sensor value when the screen turns off + mHighestLightSensorValue = -1; } } } @@ -2059,15 +2075,40 @@ class PowerManagerService extends IPowerManager.Stub } }; + private void dockStateChanged(int state) { + synchronized (mLocks) { + mIsDocked = (state != Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (mIsDocked) { + mHighestLightSensorValue = -1; + } + if ((mPowerState & SCREEN_ON_BIT) != 0) { + // force lights recalculation + int value = (int)mLightSensorValue; + mLightSensorValue = -1; + lightSensorChangedLocked(value); + } + } + } + private void lightSensorChangedLocked(int value) { if (mDebugLightSensor) { Log.d(TAG, "lightSensorChangedLocked " + value); } + // do not allow light sensor value to decrease + if (mHighestLightSensorValue < value) { + mHighestLightSensorValue = value; + } + if (mLightSensorValue != value) { mLightSensorValue = value; if ((mPowerState & BATTERY_LOW_BIT) == 0) { - int lcdValue = getAutoBrightnessValue(value, mLcdBacklightValues); + // use maximum light sensor value seen since screen went on for LCD to avoid flicker + // we only do this if we are undocked, since lighting should be stable when + // stationary in a dock. + int lcdValue = getAutoBrightnessValue( + (mIsDocked ? value : mHighestLightSensorValue), + mLcdBacklightValues); int buttonValue = getAutoBrightnessValue(value, mButtonBacklightValues); int keyboardValue; if (mKeyboardVisible) { diff --git a/test-runner/android/test/TouchUtils.java b/test-runner/android/test/TouchUtils.java index 962b2f9aab30..69c6d2d57d00 100644 --- a/test-runner/android/test/TouchUtils.java +++ b/test-runner/android/test/TouchUtils.java @@ -773,7 +773,7 @@ public class TouchUtils { float xStep = (toX - fromX) / stepCount; MotionEvent event = MotionEvent.obtain(downTime, eventTime, - MotionEvent.ACTION_DOWN, fromX, y, 0); + MotionEvent.ACTION_DOWN, x, y, 0); inst.sendPointerSync(event); inst.waitForIdleSync(); @@ -787,7 +787,7 @@ public class TouchUtils { } eventTime = SystemClock.uptimeMillis(); - event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, fromX, y, 0); + event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0); inst.sendPointerSync(event); inst.waitForIdleSync(); } diff --git a/tests/AndroidTests/Android.mk b/tests/AndroidTests/Android.mk index bff8fba2c6ac..0d29c358afde 100644 --- a/tests/AndroidTests/Android.mk +++ b/tests/AndroidTests/Android.mk @@ -5,8 +5,6 @@ LOCAL_MODULE_TAGS := tests LOCAL_JAVA_LIBRARIES := framework-tests android.test.runner services -LOCAL_STATIC_JAVA_LIBRARIES := gsf-client - # Resource unit tests use a private locale LOCAL_AAPT_FLAGS = -c xx_YY -c cs -c 160dpi -c 32dpi -c 240dpi diff --git a/tests/CoreTests/android/content/SyncQueueTest.java b/tests/CoreTests/android/content/SyncQueueTest.java index 5f4ab789bd0a..1da59d18e1a8 100644 --- a/tests/CoreTests/android/content/SyncQueueTest.java +++ b/tests/CoreTests/android/content/SyncQueueTest.java @@ -66,22 +66,22 @@ public class SyncQueueTest extends AndroidTestCase { long now = SystemClock.elapsedRealtime() + 200; - assertEquals(op6, mSyncQueue.nextReadyToRun(now)); + assertEquals(op6, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op6); - assertEquals(op1, mSyncQueue.nextReadyToRun(now)); + assertEquals(op1, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op1); - assertEquals(op4, mSyncQueue.nextReadyToRun(now)); + assertEquals(op4, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op4); - assertEquals(op5, mSyncQueue.nextReadyToRun(now)); + assertEquals(op5, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op5); - assertEquals(op2, mSyncQueue.nextReadyToRun(now)); + assertEquals(op2, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op2); - assertEquals(op3, mSyncQueue.nextReadyToRun(now)); + assertEquals(op3, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op3); } @@ -109,32 +109,32 @@ public class SyncQueueTest extends AndroidTestCase { long now = SystemClock.elapsedRealtime() + 200; - assertEquals(op6, mSyncQueue.nextReadyToRun(now)); + assertEquals(op6, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op6); - assertEquals(op1, mSyncQueue.nextReadyToRun(now)); + assertEquals(op1, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op1); mSettings.setBackoff(ACCOUNT2, AUTHORITY3, now + 200, 5); - assertEquals(op5, mSyncQueue.nextReadyToRun(now)); + assertEquals(op5, mSyncQueue.nextReadyToRun(now).first); mSettings.setBackoff(ACCOUNT2, AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0); - assertEquals(op4, mSyncQueue.nextReadyToRun(now)); + assertEquals(op4, mSyncQueue.nextReadyToRun(now).first); mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, now + 200); - assertEquals(op5, mSyncQueue.nextReadyToRun(now)); + assertEquals(op5, mSyncQueue.nextReadyToRun(now).first); mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, 0); - assertEquals(op4, mSyncQueue.nextReadyToRun(now)); + assertEquals(op4, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op4); - assertEquals(op5, mSyncQueue.nextReadyToRun(now)); + assertEquals(op5, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op5); - assertEquals(op2, mSyncQueue.nextReadyToRun(now)); + assertEquals(op2, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op2); - assertEquals(op3, mSyncQueue.nextReadyToRun(now)); + assertEquals(op3, mSyncQueue.nextReadyToRun(now).first); mSyncQueue.remove(op3); } |