Merge "Fix for 2224736. Sometimes mSeeking is reset without calling setSeekTo first."
diff --git a/Android.mk b/Android.mk
index 682f286..ab1e7ea 100644
--- a/Android.mk
+++ b/Android.mk
@@ -222,6 +222,7 @@
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 0893e4e..16ed39b 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"
@@ -135042,7 +135220,7 @@
<parameter name="error" type="int">
</parameter>
</method>
-<method name="onInit"
+<method name="onEvent"
return="void"
abstract="true"
native="false"
@@ -135052,6 +135230,10 @@
deprecated="not deprecated"
visibility="public"
>
+<parameter name="eventType" type="int">
+</parameter>
+<parameter name="params" type="android.os.Bundle">
+</parameter>
</method>
<method name="onPartialResults"
return="void"
@@ -135137,10 +135319,6 @@
>
<parameter name="context" type="android.content.Context">
</parameter>
-<parameter name="listener" type="android.speech.RecognitionListener">
-</parameter>
-<parameter name="recognizerIntent" type="android.content.Intent">
-</parameter>
</method>
<method name="destroy"
return="void"
@@ -135166,6 +135344,19 @@
<parameter name="context" type="android.content.Context">
</parameter>
</method>
+<method name="setRecognitionListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.speech.RecognitionListener">
+</parameter>
+</method>
<method name="startListening"
return="void"
abstract="false"
@@ -135190,7 +135381,7 @@
visibility="public"
>
</method>
-<field name="AUDIO_ERROR"
+<field name="ERROR_AUDIO"
type="int"
transient="false"
volatile="false"
@@ -135201,7 +135392,7 @@
visibility="public"
>
</field>
-<field name="CLIENT_ERROR"
+<field name="ERROR_CLIENT"
type="int"
transient="false"
volatile="false"
@@ -135212,7 +135403,7 @@
visibility="public"
>
</field>
-<field name="MANAGER_NOT_INITIALIZED_ERROR"
+<field name="ERROR_INSUFFICIENT_PERMISSIONS"
type="int"
transient="false"
volatile="false"
@@ -135223,7 +135414,7 @@
visibility="public"
>
</field>
-<field name="NETWORK_ERROR"
+<field name="ERROR_NETWORK"
type="int"
transient="false"
volatile="false"
@@ -135234,7 +135425,7 @@
visibility="public"
>
</field>
-<field name="NETWORK_TIMEOUT_ERROR"
+<field name="ERROR_NETWORK_TIMEOUT"
type="int"
transient="false"
volatile="false"
@@ -135245,7 +135436,7 @@
visibility="public"
>
</field>
-<field name="NO_MATCH_ERROR"
+<field name="ERROR_NO_MATCH"
type="int"
transient="false"
volatile="false"
@@ -135256,18 +135447,7 @@
visibility="public"
>
</field>
-<field name="RECOGNITION_RESULTS_STRING_ARRAY"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""recognition_results_string_array""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="SERVER_BUSY_ERROR"
+<field name="ERROR_RECOGNIZER_BUSY"
type="int"
transient="false"
volatile="false"
@@ -135278,7 +135458,7 @@
visibility="public"
>
</field>
-<field name="SERVER_ERROR"
+<field name="ERROR_SERVER"
type="int"
transient="false"
volatile="false"
@@ -135289,7 +135469,7 @@
visibility="public"
>
</field>
-<field name="SPEECH_TIMEOUT_ERROR"
+<field name="ERROR_SPEECH_TIMEOUT"
type="int"
transient="false"
volatile="false"
@@ -135300,6 +135480,213 @@
visibility="public"
>
</field>
+<field name="RESULTS_RECOGNITION"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""results_recognition""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="RecognitionService"
+ extends="android.app.Service"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="RecognitionService"
+ type="android.speech.RecognitionService"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="onBind"
+ return="android.os.IBinder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="intent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="onCancel"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="listener" type="android.speech.RecognitionService.Callback">
+</parameter>
+</method>
+<method name="onStartListening"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="recognizerIntent" type="android.content.Intent">
+</parameter>
+<parameter name="listener" type="android.speech.RecognitionService.Callback">
+</parameter>
+</method>
+<method name="onStopListening"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="listener" type="android.speech.RecognitionService.Callback">
+</parameter>
+</method>
+</class>
+<class name="RecognitionService.Callback"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="beginningOfSpeech"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="bufferReceived"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="buffer" type="byte[]">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="endOfSpeech"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="error"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="int">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="partialResults"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="partialResults" type="android.os.Bundle">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="readyForSpeech"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="params" type="android.os.Bundle">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="results"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="results" type="android.os.Bundle">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="rmsChanged"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rmsdB" type="float">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
</class>
<class name="RecognizerIntent"
extends="java.lang.Object"
@@ -192240,6 +192627,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"
@@ -200492,223 +200922,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 2ec0b70..845c854 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -106,6 +106,9 @@
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 e65cdf1..f7cb2273 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -431,6 +431,10 @@
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
new file mode 100644
index 0000000..0c8e7c1
--- /dev/null
+++ b/common/java/com/android/common/Base64.java
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.common;
+
+/**
+ * Utilities for encoding and decoding the Base64 encoding. See RFCs
+ * 2045 and 3548.
+ */
+public class Base64 {
+ /**
+ * Encoder flag bit to indicate you want the padding '='
+ * characters at the end (if any) to be omitted.
+ */
+ public static final int NO_PADDING = 1;
+
+ /**
+ * Encoder flag bit to indicate you want all line terminators to
+ * be omitted (ie, the output will be on one long line).
+ */
+ public static final int NO_WRAP = 2;
+
+ /**
+ * Encoder flag bit to indicate you want lines to be ended with
+ * CRLF instead of just LF.
+ */
+ public static final int CRLF = 4;
+
+ /**
+ * Encoder/decoder flag bit to indicate using the "web safe"
+ * variant of Base64 (see RFC 3548 section 4) where '-' and '_'
+ * are used in place of '+' and '/'.
+ */
+ public static final int WEB_SAFE = 8;
+
+ /**
+ * Lookup table for turning bytes into their position in the
+ * Base64 alphabet.
+ */
+ private static final int DECODE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 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, -1, -1, -1, -1, -1,
+ -1, 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, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ /**
+ * Decode lookup table for the "web safe" variant (RFC 3548
+ * sec. 4) where - and _ replace + and /.
+ */
+ private static final int DECODE_WEBSAFE[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
+ -1, 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, -1, -1, -1, -1, 63,
+ -1, 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, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ /** Non-data values in the DECODE arrays. */
+ private static final int SKIP = -1;
+ private static final int EQUALS = -2;
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @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.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(String str, int flags) {
+ return decode(str.getBytes(), flags);
+ }
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the input array to decode
+ * @param flags controls certain features of the decoded output.
+ * Passing 0 to decode standard Base64.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int flags) {
+ return decode(input, 0, input.length, flags);
+ }
+
+ /**
+ * Decode the Base64-encoded data in input and return the data in
+ * a new byte array.
+ *
+ * The padding '=' characters at the end are considered optional, but
+ * if any are present, there must be the correct number of them.
+ *
+ * @param input the data to decode
+ * @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.
+ *
+ * @throws IllegalArgumentException if the input contains
+ * incorrect padding
+ */
+ public static byte[] decode(byte[] input, int offset, int len, int flags) {
+ int p = offset;
+ // Allocate space for the most data the input could represent.
+ // (It could contain less if it contains whitespace, etc.)
+ byte[] output = new byte[len*3/4];
+ len += offset;
+ int op = 0;
+
+ final int[] decode = ((flags & WEB_SAFE) == 0) ?
+ DECODE : DECODE_WEBSAFE;
+
+ int state = 0;
+ int value = 0;
+
+ while (p < len) {
+
+ // Try the fast path: we're starting a new tuple and the
+ // next four bytes of the input stream are all data
+ // bytes. This corresponds to going through states
+ // 0-1-2-3-0. We expect to use this method for most of
+ // the data.
+ //
+ // If any of the next four bytes of input are non-data
+ // (whitespace, etc.), value will end up negative. (All
+ // the non-data values in decode are small negative
+ // numbers, so shifting any of them up and or'ing them
+ // together will result in a value with its top bit set.)
+ //
+ // You can remove this whole block and the output should
+ // be the same, just slower.
+ if (state == 0 && p+4 <= len &&
+ (value = ((decode[input[p] & 0xff] << 18) |
+ (decode[input[p+1] & 0xff] << 12) |
+ (decode[input[p+2] & 0xff] << 6) |
+ (decode[input[p+3] & 0xff]))) >= 0) {
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ p += 4;
+ continue;
+ }
+
+ // The fast path isn't available -- either we've read a
+ // partial tuple, or the next four input bytes aren't all
+ // data, or whatever. Fall back to the slower state
+ // machine implementation.
+ //
+ // States 0-3 are reading through the next input tuple.
+ // State 4 is having read one '=' and expecting exactly
+ // one more.
+ // State 5 is expecting no more data or padding characters
+ // in the input.
+
+ int d = decode[input[p++] & 0xff];
+
+ switch (state) {
+ case 0:
+ if (d >= 0) {
+ value = d;
+ ++state;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 1:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 2:
+ if (d >= 0) {
+ value = (value << 6) | d;
+ ++state;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect exactly one more padding character.
+ output[op++] = (byte) (value >> 4);
+ state = 4;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 3:
+ if (d >= 0) {
+ // Emit the output triple and return to state 0.
+ value = (value << 6) | d;
+ output[op+2] = (byte) value;
+ output[op+1] = (byte) (value >> 8);
+ output[op] = (byte) (value >> 16);
+ op += 3;
+ state = 0;
+ } else if (d == EQUALS) {
+ // Emit the last (partial) output tuple;
+ // expect no further data or padding characters.
+ output[op+1] = (byte) (value >> 2);
+ output[op] = (byte) (value >> 10);
+ op += 2;
+ state = 5;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 4:
+ if (d == EQUALS) {
+ ++state;
+ } else if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+
+ case 5:
+ if (d != SKIP) {
+ throw new IllegalArgumentException("bad base-64");
+ }
+ break;
+ }
+ }
+
+ // Done reading input. Now figure out where we are left in
+ // the state machine and finish up.
+
+ switch (state) {
+ case 0:
+ // Output length is a multiple of three. Fine.
+ break;
+ case 1:
+ // Read one extra input byte, which isn't enough to
+ // make another output byte. Illegal.
+ throw new IllegalArgumentException("bad base-64");
+ case 2:
+ // Read two extra input bytes, enough to emit 1 more
+ // output byte. Fine.
+ output[op++] = (byte) (value >> 4);
+ break;
+ case 3:
+ // Read three extra input bytes, enough to emit 2 more
+ // output bytes. Fine.
+ output[op+1] = (byte) (value >> 2);
+ output[op] = (byte) (value >> 10);
+ op += 2;
+ break;
+ case 4:
+ // Read one padding '=' when we expected 2. Illegal.
+ throw new IllegalArgumentException("bad base-64");
+ case 5:
+ // Read all the padding '='s we expected and no more.
+ // Fine.
+ break;
+ }
+
+ // Maybe we got lucky and allocated exactly enough output space.
+ if (op == output.length) {
+ return output;
+ }
+
+ // Need to shorten the array, so allocate a new one of the
+ // right size and copy.
+ byte[] temp = new byte[op];
+ System.arraycopy(output, 0, temp, 0, op);
+ return temp;
+ }
+
+ /**
+ * Emit a new line every this many output tuples. Corresponds to
+ * a 76-character line length (the maximum allowable according to
+ * RFC 2045).
+ */
+ private static final int LINE_GROUPS = 19;
+
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/',
+ };
+
+ /**
+ * Lookup table for turning Base64 alphabet positions (6 bits)
+ * into output bytes.
+ */
+ private static final byte ENCODE_WEBSAFE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '-', '_',
+ };
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @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.
+ */
+ public static String encodeToString(byte[] input, int flags) {
+ return new String(encode(input, flags));
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * String with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * 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.
+ */
+ public static String encodeToString(byte[] input, int offset, int len, int flags) {
+ return new String(encode(input, offset, len, flags));
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @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.
+ */
+ public static byte[] encode(byte[] input, int flags) {
+ return encode(input, 0, input.length, flags);
+ }
+
+ /**
+ * Base64-encode the given data and return a newly allocated
+ * byte[] with the result.
+ *
+ * @param input the data to encode
+ * @param offset the position within the input array at which to
+ * 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.
+ */
+ public static byte[] encode(byte[] input, int offset, int len, int flags) {
+ final boolean do_padding = (flags & NO_PADDING) == 0;
+ final boolean do_newline = (flags & NO_WRAP) == 0;
+ final boolean do_cr = (flags & CRLF) != 0;
+
+ final byte[] encode = ((flags & WEB_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE;
+
+ // Compute the exact length of the array we will produce.
+ int output_len = len / 3 * 4;
+
+ // Account for the tail of the data and the padding bytes, if any.
+ if (do_padding) {
+ if (len % 3 > 0) {
+ output_len += 4;
+ }
+ } else {
+ switch (len % 3) {
+ case 0: break;
+ case 1: output_len += 2; break;
+ case 2: output_len += 3; break;
+ }
+ }
+
+ // Account for the newlines, if any.
+ if (do_newline && len > 0) {
+ output_len += (((len-1) / (3 * LINE_GROUPS)) + 1) * (do_cr ? 2 : 1);
+ }
+
+ int op = 0;
+ byte[] output = new byte[output_len];
+
+ // The main loop, turning 3 input bytes into 4 output bytes on
+ // each iteration.
+ int count = do_newline ? LINE_GROUPS : -1;
+ int p = offset;
+ len += offset;
+ while (p+3 <= len) {
+ int v = ((input[p++] & 0xff) << 16) |
+ ((input[p++] & 0xff) << 8) |
+ (input[p++] & 0xff);
+ output[op++] = encode[(v >> 18) & 0x3f];
+ output[op++] = encode[(v >> 12) & 0x3f];
+ output[op++] = encode[(v >> 6) & 0x3f];
+ output[op++] = encode[v & 0x3f];
+ if (--count == 0) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ count = LINE_GROUPS;
+ }
+ }
+
+ // Finish up the tail of the input.
+ if (p == len-1) {
+ int v = (input[p] & 0xff) << 4;
+ output[op++] = encode[(v >> 6) & 0x3f];
+ output[op++] = encode[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (p == len-2) {
+ int v = ((input[p] & 0xff) << 10) | ((input[p+1] & 0xff) << 2);
+ output[op++] = encode[(v >> 12) & 0x3f];
+ output[op++] = encode[(v >> 6) & 0x3f];
+ output[op++] = encode[v & 0x3f];
+ if (do_padding) {
+ output[op++] = '=';
+ }
+ if (do_newline) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+ } else if (do_newline && op > 0 && count != LINE_GROUPS) {
+ if (do_cr) output[op++] = '\r';
+ output[op++] = '\n';
+ }
+
+ assert op == output.length;
+ return output;
+ }
+}
diff --git a/common/java/com/android/common/OperationScheduler.java b/common/java/com/android/common/OperationScheduler.java
index 71b22ce..c7b12d3 100644
--- a/common/java/com/android/common/OperationScheduler.java
+++ b/common/java/com/android/common/OperationScheduler.java
@@ -192,7 +192,6 @@
/**
* Forbid any operations until after a certain (absolute) time.
- * Commonly used when a server returns a "Retry-After:" type directive.
* Limited by {@link #Options.maxMoratoriumMillis}.
*
* @param millis wall clock time ({@link System#currentTimeMillis()}) to
@@ -206,6 +205,29 @@
}
/**
+ * Forbid any operations until after a certain time, as specified in
+ * the format used by the HTTP "Retry-After" header.
+ * Limited by {@link #Options.maxMoratoriumMillis}.
+ *
+ * @param retryAfter moratorium time in HTTP format
+ * @return true if a time was successfully parsed
+ */
+ public boolean setMoratoriumTimeHttp(String retryAfter) {
+ try {
+ long ms = Long.valueOf(retryAfter) * 1000;
+ setMoratoriumTimeMillis(ms + System.currentTimeMillis());
+ return true;
+ } catch (NumberFormatException nfe) {
+ try {
+ setMoratoriumTimeMillis(HttpDateTime.parse(retryAfter));
+ return true;
+ } catch (IllegalArgumentException iae) {
+ return false;
+ }
+ }
+ }
+
+ /**
* Enable or disable all operations. When disabled, all calls to
* {@link #getNextTimeMillis()} return {@link Long#MAX_VALUE}.
* Commonly used when data network availability goes up and down.
diff --git a/common/tests/src/com/android/common/Base64Test.java b/common/tests/src/com/android/common/Base64Test.java
new file mode 100644
index 0000000..5c9712a
--- /dev/null
+++ b/common/tests/src/com/android/common/Base64Test.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.common;
+
+import junit.framework.TestCase;
+
+public class Base64Test extends TestCase {
+ private static final String TAG = "B64Test";
+
+ /** Decodes a string, returning a string. */
+ private String decodeString(String in) throws Exception {
+ byte[] out = Base64.decode(in, 0);
+ return new String(out);
+ }
+
+ /**
+ * Encodes the string 'in' using 'flags'. Asserts that decoding
+ * gives the same string. Returns the encoded string.
+ */
+ private String encodeToString(String in, int flags) throws Exception {
+ String b64 = Base64.encodeToString(in.getBytes(), flags);
+ String dec = decodeString(b64);
+ assertEquals(in, dec);
+ return b64;
+ }
+
+ /** Assert that decoding 'in' throws IllegalArgumentException. */
+ private void assertBad(String in) throws Exception {
+ try {
+ byte[] out = Base64.decode(in, 0);
+ fail("should have failed to decode");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ /** Assert that actual equals the first len bytes of expected. */
+ private void assertEquals(byte[] expected, int len, byte[] actual) {
+ assertEquals(len, actual.length);
+ for (int i = 0; i < len; ++i) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ public void testDecodeExtraChars() throws Exception {
+ // padding 0
+ assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
+ assertBad("aGVsbG8sIHdvcmxk=");
+ assertBad("aGVsbG8sIHdvcmxk==");
+ assertBad("aGVsbG8sIHdvcmxk =");
+ assertBad("aGVsbG8sIHdvcmxk = = ");
+ assertEquals("hello, world", decodeString(" aGVs bG8s IHdv cmxk "));
+ assertEquals("hello, world", decodeString(" aGV sbG8 sIHd vcmx k "));
+ assertEquals("hello, world", decodeString(" aG VsbG 8sIH dvcm xk "));
+ assertEquals("hello, world", decodeString(" a GVsb G8sI Hdvc mxk "));
+ assertEquals("hello, world", decodeString(" a G V s b G 8 s I H d v c m x k "));
+ assertEquals("hello, world", decodeString("_a*G_V*s_b*G_8*s_I*H_d*v_c*m_x*k_"));
+ assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
+
+ // padding 1
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE"));
+ assertBad("aGVsbG8sIHdvcmxkPyE==");
+ assertBad("aGVsbG8sIHdvcmxkPyE ==");
+ assertBad("aGVsbG8sIHdvcmxkPyE = = ");
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E"));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E ="));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E "));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E = "));
+ assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E "));
+
+ // padding 2
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg=="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg"));
+ assertBad("aGVsbG8sIHdvcmxkLg=");
+ assertBad("aGVsbG8sIHdvcmxkLg =");
+ assertBad("aGVsbG8sIHdvcmxkLg = ");
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g=="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g"));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g =="));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g "));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g = = "));
+ assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g "));
+ }
+
+ private static final byte[] BYTES = { (byte) 0xff, (byte) 0xee, (byte) 0xdd,
+ (byte) 0xcc, (byte) 0xbb, (byte) 0xaa,
+ (byte) 0x99, (byte) 0x88, (byte) 0x77 };
+
+ public void testBinaryDecode() throws Exception {
+ assertEquals(BYTES, 0, Base64.decode("", 0));
+ assertEquals(BYTES, 1, Base64.decode("/w==", 0));
+ assertEquals(BYTES, 2, Base64.decode("/+4=", 0));
+ assertEquals(BYTES, 3, Base64.decode("/+7d", 0));
+ assertEquals(BYTES, 4, Base64.decode("/+7dzA==", 0));
+ assertEquals(BYTES, 5, Base64.decode("/+7dzLs=", 0));
+ assertEquals(BYTES, 6, Base64.decode("/+7dzLuq", 0));
+ assertEquals(BYTES, 7, Base64.decode("/+7dzLuqmQ==", 0));
+ assertEquals(BYTES, 8, Base64.decode("/+7dzLuqmYg=", 0));
+ }
+
+ public void testWebSafe() throws Exception {
+ assertEquals(BYTES, 0, Base64.decode("", Base64.WEB_SAFE));
+ assertEquals(BYTES, 1, Base64.decode("_w==", Base64.WEB_SAFE));
+ assertEquals(BYTES, 2, Base64.decode("_-4=", Base64.WEB_SAFE));
+ assertEquals(BYTES, 3, Base64.decode("_-7d", Base64.WEB_SAFE));
+ assertEquals(BYTES, 4, Base64.decode("_-7dzA==", Base64.WEB_SAFE));
+ assertEquals(BYTES, 5, Base64.decode("_-7dzLs=", Base64.WEB_SAFE));
+ assertEquals(BYTES, 6, Base64.decode("_-7dzLuq", Base64.WEB_SAFE));
+ assertEquals(BYTES, 7, Base64.decode("_-7dzLuqmQ==", Base64.WEB_SAFE));
+ assertEquals(BYTES, 8, Base64.decode("_-7dzLuqmYg=", Base64.WEB_SAFE));
+
+ assertEquals("", Base64.encodeToString(BYTES, 0, 0, Base64.WEB_SAFE));
+ assertEquals("_w==\n", Base64.encodeToString(BYTES, 0, 1, Base64.WEB_SAFE));
+ assertEquals("_-4=\n", Base64.encodeToString(BYTES, 0, 2, Base64.WEB_SAFE));
+ assertEquals("_-7d\n", Base64.encodeToString(BYTES, 0, 3, Base64.WEB_SAFE));
+ assertEquals("_-7dzA==\n", Base64.encodeToString(BYTES, 0, 4, Base64.WEB_SAFE));
+ assertEquals("_-7dzLs=\n", Base64.encodeToString(BYTES, 0, 5, Base64.WEB_SAFE));
+ assertEquals("_-7dzLuq\n", Base64.encodeToString(BYTES, 0, 6, Base64.WEB_SAFE));
+ assertEquals("_-7dzLuqmQ==\n", Base64.encodeToString(BYTES, 0, 7, Base64.WEB_SAFE));
+ assertEquals("_-7dzLuqmYg=\n", Base64.encodeToString(BYTES, 0, 8, Base64.WEB_SAFE));
+ }
+
+ public void testFlags() throws Exception {
+ assertEquals("YQ==\n", encodeToString("a", 0));
+ assertEquals("YQ==", encodeToString("a", Base64.NO_WRAP));
+ assertEquals("YQ\n", encodeToString("a", Base64.NO_PADDING));
+ assertEquals("YQ", encodeToString("a", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YQ==\r\n", encodeToString("a", Base64.CRLF));
+ assertEquals("YQ\r\n", encodeToString("a", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWI=\n", encodeToString("ab", 0));
+ assertEquals("YWI=", encodeToString("ab", Base64.NO_WRAP));
+ assertEquals("YWI\n", encodeToString("ab", Base64.NO_PADDING));
+ assertEquals("YWI", encodeToString("ab", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWI=\r\n", encodeToString("ab", Base64.CRLF));
+ assertEquals("YWI\r\n", encodeToString("ab", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWJj\n", encodeToString("abc", 0));
+ assertEquals("YWJj", encodeToString("abc", Base64.NO_WRAP));
+ assertEquals("YWJj\n", encodeToString("abc", Base64.NO_PADDING));
+ assertEquals("YWJj", encodeToString("abc", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWJj\r\n", encodeToString("abc", Base64.CRLF));
+ assertEquals("YWJj\r\n", encodeToString("abc", Base64.CRLF | Base64.NO_PADDING));
+
+ assertEquals("YWJjZA==\n", encodeToString("abcd", 0));
+ assertEquals("YWJjZA==", encodeToString("abcd", Base64.NO_WRAP));
+ assertEquals("YWJjZA\n", encodeToString("abcd", Base64.NO_PADDING));
+ assertEquals("YWJjZA", encodeToString("abcd", Base64.NO_PADDING | Base64.NO_WRAP));
+ assertEquals("YWJjZA==\r\n", encodeToString("abcd", Base64.CRLF));
+ assertEquals("YWJjZA\r\n", encodeToString("abcd", Base64.CRLF | Base64.NO_PADDING));
+ }
+
+ public void testLineLength() throws Exception {
+ String in_56 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd";
+ String in_57 = in_56 + "e";
+ String in_58 = in_56 + "ef";
+ String in_59 = in_56 + "efg";
+ String in_60 = in_56 + "efgh";
+ String in_61 = in_56 + "efghi";
+
+ String prefix = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFi";
+ String out_56 = prefix + "Y2Q=\n";
+ String out_57 = prefix + "Y2Rl\n";
+ String out_58 = prefix + "Y2Rl\nZg==\n";
+ String out_59 = prefix + "Y2Rl\nZmc=\n";
+ String out_60 = prefix + "Y2Rl\nZmdo\n";
+ String out_61 = prefix + "Y2Rl\nZmdoaQ==\n";
+
+ // no newline for an empty input array.
+ assertEquals("", encodeToString("", 0));
+
+ assertEquals(out_56, encodeToString(in_56, 0));
+ assertEquals(out_57, encodeToString(in_57, 0));
+ assertEquals(out_58, encodeToString(in_58, 0));
+ assertEquals(out_59, encodeToString(in_59, 0));
+ assertEquals(out_60, encodeToString(in_60, 0));
+ assertEquals(out_61, encodeToString(in_61, 0));
+
+ assertEquals(out_56.replaceAll("=", ""), encodeToString(in_56, Base64.NO_PADDING));
+ assertEquals(out_57.replaceAll("=", ""), encodeToString(in_57, Base64.NO_PADDING));
+ assertEquals(out_58.replaceAll("=", ""), encodeToString(in_58, Base64.NO_PADDING));
+ assertEquals(out_59.replaceAll("=", ""), encodeToString(in_59, Base64.NO_PADDING));
+ assertEquals(out_60.replaceAll("=", ""), encodeToString(in_60, Base64.NO_PADDING));
+ assertEquals(out_61.replaceAll("=", ""), encodeToString(in_61, Base64.NO_PADDING));
+
+ assertEquals(out_56.replaceAll("\n", ""), encodeToString(in_56, Base64.NO_WRAP));
+ assertEquals(out_57.replaceAll("\n", ""), encodeToString(in_57, Base64.NO_WRAP));
+ assertEquals(out_58.replaceAll("\n", ""), encodeToString(in_58, Base64.NO_WRAP));
+ assertEquals(out_59.replaceAll("\n", ""), encodeToString(in_59, Base64.NO_WRAP));
+ assertEquals(out_60.replaceAll("\n", ""), encodeToString(in_60, Base64.NO_WRAP));
+ assertEquals(out_61.replaceAll("\n", ""), encodeToString(in_61, Base64.NO_WRAP));
+ }
+}
diff --git a/common/tests/src/com/android/common/OperationSchedulerTest.java b/common/tests/src/com/android/common/OperationSchedulerTest.java
index 13f710d..28178b5 100644
--- a/common/tests/src/com/android/common/OperationSchedulerTest.java
+++ b/common/tests/src/com/android/common/OperationSchedulerTest.java
@@ -113,4 +113,32 @@
"OperationScheduler.Options[backoff=10.0+2.5 max=12345.6 min=7.0 period=3800.0]",
OperationScheduler.parseOptions("", options).toString());
}
+
+ public void testMoratoriumWithHttpDate() throws Exception {
+ String name = "OperationSchedulerTest.testMoratoriumWithHttpDate";
+ SharedPreferences storage = getContext().getSharedPreferences(name, 0);
+ storage.edit().clear().commit();
+
+ OperationScheduler scheduler = new OperationScheduler(storage);
+ OperationScheduler.Options options = new OperationScheduler.Options();
+
+ long beforeTrigger = System.currentTimeMillis();
+ scheduler.setTriggerTimeMillis(beforeTrigger + 1000000);
+ assertEquals(beforeTrigger + 1000000, scheduler.getNextTimeMillis(options));
+
+ scheduler.setMoratoriumTimeMillis(beforeTrigger + 2000000);
+ assertEquals(beforeTrigger + 2000000, scheduler.getNextTimeMillis(options));
+
+ long beforeMoratorium = System.currentTimeMillis();
+ assertTrue(scheduler.setMoratoriumTimeHttp("3000"));
+ long afterMoratorium = System.currentTimeMillis();
+ assertTrue(beforeMoratorium + 3000000 <= scheduler.getNextTimeMillis(options));
+ assertTrue(afterMoratorium + 3000000 >= scheduler.getNextTimeMillis(options));
+
+ options.maxMoratoriumMillis = Long.MAX_VALUE / 2;
+ assertTrue(scheduler.setMoratoriumTimeHttp("Fri, 31 Dec 2030 23:59:59 GMT"));
+ assertEquals(1924991999000L, scheduler.getNextTimeMillis(options));
+
+ assertFalse(scheduler.setMoratoriumTimeHttp("not actually a date"));
+ }
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 414d963..19e741a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -271,7 +271,7 @@
}
/**
- * 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 @@
* 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 @@
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 @@
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/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1af6d6e..56e44c8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -292,7 +292,7 @@
if (mAppDir == null) {
if (mSystemContext == null) {
mSystemContext =
- ApplicationContext.createSystemContext(mainThread);
+ ContextImpl.createSystemContext(mainThread);
mSystemContext.getResources().updateConfiguration(
mainThread.getConfiguration(),
mainThread.getDisplayMetricsLocked(false));
@@ -513,7 +513,7 @@
try {
java.lang.ClassLoader cl = getClassLoader();
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(this, null, mActivityThread);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
@@ -1145,7 +1145,7 @@
}
}
- private static ApplicationContext mSystemContext = null;
+ private static ContextImpl mSystemContext = null;
private static final class ActivityRecord {
IBinder token;
@@ -1308,7 +1308,7 @@
}
private static final class ContextCleanupInfo {
- ApplicationContext context;
+ ContextImpl context;
String what;
String who;
}
@@ -1629,7 +1629,7 @@
long dalvikAllocated = dalvikMax - dalvikFree;
long viewInstanceCount = ViewDebug.getViewInstanceCount();
long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount();
- long appContextInstanceCount = ApplicationContext.getInstanceCount();
+ long appContextInstanceCount = ContextImpl.getInstanceCount();
long activityInstanceCount = Activity.getInstanceCount();
int globalAssetCount = AssetManager.getGlobalAssetCount();
int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
@@ -2253,11 +2253,11 @@
return mBoundApplication.processName;
}
- public ApplicationContext getSystemContext() {
+ public ContextImpl getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
- ApplicationContext context =
- ApplicationContext.createSystemContext(this);
+ ContextImpl context =
+ ContextImpl.createSystemContext(this);
PackageInfo info = new PackageInfo(this, "android", context, null);
context.init(info, null, this);
context.getResources().updateConfiguration(
@@ -2272,7 +2272,7 @@
public void installSystemApplicationInfo(ApplicationInfo info) {
synchronized (this) {
- ApplicationContext context = getSystemContext();
+ ContextImpl context = getSystemContext();
context.init(new PackageInfo(this, "android", context, info), null, this);
}
}
@@ -2387,7 +2387,7 @@
}
}
- final void scheduleContextCleanup(ApplicationContext context, String who,
+ final void scheduleContextCleanup(ContextImpl context, String who,
String what) {
ContextCleanupInfo cci = new ContextCleanupInfo();
cci.context = context;
@@ -2446,7 +2446,7 @@
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(r.packageInfo, r.token, this);
appContext.setOuterContext(activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
@@ -2643,7 +2643,7 @@
+ ", comp=" + data.intent.getComponent().toShortString()
+ ", dir=" + packageInfo.getAppDir());
- ApplicationContext context = (ApplicationContext)app.getBaseContext();
+ ContextImpl context = (ContextImpl)app.getBaseContext();
receiver.setOrderedHint(true);
receiver.setResult(data.resultCode, data.resultData,
data.resultExtras);
@@ -2712,7 +2712,7 @@
if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent "
+ data.appInfo.backupAgentName);
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
context.setOuterContext(agent);
agent.attach(context);
@@ -2784,7 +2784,7 @@
try {
if (localLOGV) Log.v(TAG, "Creating service " + data.info.name);
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(packageInfo, null, this);
Application app = packageInfo.makeApplication(false, mInstrumentation);
@@ -2910,9 +2910,9 @@
if (localLOGV) Log.v(TAG, "Destroying service " + s);
s.onDestroy();
Context context = s.getBaseContext();
- if (context instanceof ApplicationContext) {
+ if (context instanceof ContextImpl) {
final String who = s.getClassName();
- ((ApplicationContext) context).scheduleFinalCleanup(who, "Service");
+ ((ContextImpl) context).scheduleFinalCleanup(who, "Service");
}
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
@@ -3527,8 +3527,8 @@
// ApplicationContext we need to have it tear down things
// cleanly.
Context c = r.activity.getBaseContext();
- if (c instanceof ApplicationContext) {
- ((ApplicationContext) c).scheduleFinalCleanup(
+ if (c instanceof ContextImpl) {
+ ((ContextImpl) c).scheduleFinalCleanup(
r.activity.getClass().getName(), "Activity");
}
}
@@ -3790,7 +3790,7 @@
Resources.updateSystemConfiguration(config, dm);
- ApplicationContext.ApplicationPackageManager.configurationChanged();
+ ContextImpl.ApplicationPackageManager.configurationChanged();
//Log.i(TAG, "Configuration changed in " + currentPackageName());
{
Iterator<WeakReference<Resources>> it =
@@ -3942,7 +3942,7 @@
}
if (data.instrumentationName != null) {
- ApplicationContext appContext = new ApplicationContext();
+ ContextImpl appContext = new ContextImpl();
appContext.init(data.info, null, this);
InstrumentationInfo ii = null;
try {
@@ -3967,7 +3967,7 @@
instrApp.dataDir = ii.dataDir;
PackageInfo pi = getPackageInfo(instrApp,
appContext.getClassLoader(), false, true);
- ApplicationContext instrContext = new ApplicationContext();
+ ContextImpl instrContext = new ContextImpl();
instrContext.init(pi, null, this);
try {
@@ -4337,7 +4337,7 @@
android.ddm.DdmHandleAppName.setAppName("system_process");
try {
mInstrumentation = new Instrumentation();
- ApplicationContext context = new ApplicationContext();
+ ContextImpl context = new ContextImpl();
context.init(getSystemContext().mPackageInfo, null, this);
Application app = Instrumentation.newApplication(Application.class, context);
mAllApplications.add(app);
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index 53c7935..9082003 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -277,7 +277,26 @@
} 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/ApplicationContext.java b/core/java/android/app/ContextImpl.java
similarity index 99%
rename from core/java/android/app/ApplicationContext.java
rename to core/java/android/app/ContextImpl.java
index cf6e0e7..5f89496 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ContextImpl.java
@@ -148,10 +148,10 @@
}
/**
- * Common implementation of Context API, which Activity and other application
- * classes inherit.
+ * Common implementation of Context API, which provides the base
+ * context object for Activity and other application components.
*/
-class ApplicationContext extends Context {
+class ContextImpl extends Context {
private final static String TAG = "ApplicationContext";
private final static boolean DEBUG = false;
private final static boolean DEBUG_ICONS = false;
@@ -1328,13 +1328,13 @@
public Context createPackageContext(String packageName, int flags)
throws PackageManager.NameNotFoundException {
if (packageName.equals("system") || packageName.equals("android")) {
- return new ApplicationContext(mMainThread.getSystemContext());
+ return new ContextImpl(mMainThread.getSystemContext());
}
ActivityThread.PackageInfo pi =
mMainThread.getPackageInfo(packageName, flags);
if (pi != null) {
- ApplicationContext c = new ApplicationContext();
+ ContextImpl c = new ContextImpl();
c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
c.init(pi, null, mMainThread, mResources);
if (c.mResources != null) {
@@ -1371,13 +1371,13 @@
return file;
}
- static ApplicationContext createSystemContext(ActivityThread mainThread) {
- ApplicationContext context = new ApplicationContext();
+ static ContextImpl createSystemContext(ActivityThread mainThread) {
+ ContextImpl context = new ContextImpl();
context.init(Resources.getSystem(), mainThread);
return context;
}
- ApplicationContext() {
+ ContextImpl() {
++sInstanceCount;
mOuterContext = this;
}
@@ -1388,7 +1388,7 @@
*
* @param context Existing application context.
*/
- public ApplicationContext(ApplicationContext context) {
+ public ContextImpl(ContextImpl context) {
++sInstanceCount;
mPackageInfo = context.mPackageInfo;
mResources = context.mResources;
@@ -2124,7 +2124,7 @@
}
}
- ApplicationPackageManager(ApplicationContext context,
+ ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;
mPM = pm;
@@ -2656,7 +2656,7 @@
}
}
- private final ApplicationContext mContext;
+ private final ContextImpl mContext;
private final IPackageManager mPM;
private static final Object sSync = new Object();
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index cb42236..edb40ed 100755
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -27,6 +27,7 @@
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 eb2d7b1..b5587ed 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -42,6 +42,7 @@
import java.io.OutputStream;
import java.util.List;
import java.util.ArrayList;
+import java.util.Collection;
/**
@@ -966,6 +967,65 @@
}
/**
+ * 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 974a667..e0dfab5 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -32,6 +32,8 @@
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 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 b0f14c1..2d906ed 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -21,6 +21,7 @@
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 @@
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 @@
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 @@
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 @@
* 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/content/PeriodicSync.aidl b/core/java/android/content/PeriodicSync.aidl
new file mode 100644
index 0000000..4530591
--- /dev/null
+++ b/core/java/android/content/PeriodicSync.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable PeriodicSync;
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
new file mode 100644
index 0000000..17813ec
--- /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 699b61d..619c7d5 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -52,14 +52,7 @@
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 @@
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 @@
// 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 @@
// ignore the rest of the states -- leave our boolean alone.
}
if (mDataConnectionIsConnected) {
- initializeSyncPoll();
sendCheckAlarmsMessage();
}
}
@@ -291,14 +275,8 @@
};
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 @@
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 @@
}
}
- 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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;
+ // 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;
+ }
- if (processedAccounts.contains(curAccount)) {
- continue;
- }
-
- processedAccounts.add(curAccount);
-
- 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 @@
}
}
+ 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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 a9f15d9..2eead3a 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -2,8 +2,6 @@
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 @@
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 @@
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 @@
* 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) {
+ if (best == 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) {
- return null;
- }
- return nextOpAndRunTime.second;
+ return Pair.create(best, bestRunTime);
}
/**
@@ -158,21 +138,25 @@
* 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 b8fda03..bb2b2da 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -20,10 +20,12 @@
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 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 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 @@
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 @@
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 @@
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 db70096..07a1f46 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -36,7 +36,6 @@
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.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 @@
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 @@
/** 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 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 @@
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 @@
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 @@
private int mYearInDays;
private final Context mContext;
+
private static volatile SyncStorageEngine sSyncStorageEngine = null;
/**
@@ -262,17 +272,15 @@
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 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 @@
}
} 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 @@
authority.backoffDelay = nextDelay;
changed = true;
}
- if (changed) {
- writeAccountInfoLocked();
- }
}
if (changed) {
@@ -499,12 +507,12 @@
+ " -> 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 @@
}
}
+ 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 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 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 @@
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;
}
- 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() == 3) {
+ if ("periodicSync".equals(tagName) && authority != null) {
+ periodicSync = parsePeriodicSync(parser, authority);
+ }
+ } else if (parser.getDepth() == 4 && periodicSync != null) {
+ if ("extra".equals(tagName)) {
+ parseExtra(parser, periodicSync);
}
}
}
@@ -1249,6 +1375,105 @@
}
}
+ 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 @@
} 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 @@
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/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index e4b0191..22e2a83 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1922,8 +1922,9 @@
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();
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index f89ba91..cb42d73 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -184,6 +184,20 @@
* <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 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/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 08ab166..348e9e8 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -629,6 +629,14 @@
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
/**
+ * The permission to access downloads to {@link DESTINATION_EXTERNAL}
+ * which were downloaded by other applications.
+ * @hide
+ */
+ public static final String PERMISSION_SEE_ALL_EXTERNAL =
+ "android.permission.SEE_ALL_EXTERNAL";
+
+ /**
* The content:// URI for the data table in the provider
*/
public static final Uri CONTENT_URI =
@@ -1101,28 +1109,5 @@
* This download doesn't show in the UI or in the notifications.
*/
public static final int VISIBILITY_HIDDEN = 2;
-
- /**
- * Using a file name, create a title for a download. Then store it in
- * the database and return it.
- *
- * @param context Context for reaching the {@link ContentResolver} so
- * the database can be updated with the new title.
- * @param filename Full path to the file. Used to generate a title.
- * @param id Id of the download, so the new title can be stored in the
- * database
- * @return String Newly created title.
- * @hide
- */
- public static String createTitleFromFilename(Context context,
- String filename, long id) {
- if (filename == null) return null;
- String title = new File(filename).getName();
- ContentValues values = new ContentValues();
- values.put(COLUMN_TITLE, title);
- context.getContentResolver().update(ContentUris.withAppendedId(
- CONTENT_URI, id), values, null, null);
- return title;
- }
}
}
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 5b48bd2..2a0f986 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -19,12 +19,12 @@
import android.os.Bundle;
/**
- * Listener for speech recognition events, used with RecognitionService.
+ * Listener for speech recognition events, used with RecognitionService.
* This gives you both the final recognition results, as well as various
* intermediate events that can be used to show visual feedback to the user.
* {@hide}
*/
-interface IRecognitionListener {
+oneway interface IRecognitionListener {
/**
* Called when the endpointer is ready for the user to start speaking.
*
@@ -76,4 +76,12 @@
* @param results a Bundle containing the current most likely result.
*/
void onPartialResults(in Bundle results);
+
+ /**
+ * Reserved for adding future events.
+ *
+ * @param eventType the type of the occurred event
+ * @param params a Bundle containing the passed parameters
+ */
+ void onEvent(in int eventType, in Bundle params);
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index ca9af15..8b27e63 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -29,7 +29,7 @@
* accessing recognition service.
* {@hide}
*/
-interface IRecognitionService {
+oneway interface IRecognitionService {
/**
* Starts listening for speech. Please note that the recognition service supports
* one listener only, therefore, if this function is called from two different threads,
@@ -38,7 +38,7 @@
* @param recognizerIntent the intent from which the invocation occurred. Additionally,
* this intent can contain extra parameters to manipulate the behavior of the recognition
* client. For more information see {@link RecognizerIntent}.
- * @param listener to receive callbacks
+ * @param listener to receive callbacks, note that this must be non-null
*/
void startListening(in Intent recognizerIntent, in IRecognitionListener listener);
@@ -46,11 +46,15 @@
* Stops listening for speech. Speech captured so far will be recognized as
* if the user had stopped speaking at this point. The function has no effect unless it
* is called during the speech capturing.
+ *
+ * @param listener to receive callbacks, note that this must be non-null
*/
- void stopListening();
+ void stopListening(in IRecognitionListener listener);
/**
* Cancels the speech recognition.
+ *
+ * @param listener to receive callbacks, note that this must be non-null
*/
- void cancel();
+ void cancel(in IRecognitionListener listener);
}
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
index eab3f40..5434887 100644
--- a/core/java/android/speech/RecognitionListener.java
+++ b/core/java/android/speech/RecognitionListener.java
@@ -24,12 +24,6 @@
* Application main thread.
*/
public interface RecognitionListener {
-
- /**
- * Called when RecognitionManager is successfully initialized
- */
- void onInit();
-
/**
* Called when the endpointer is ready for the user to start speaking.
*
@@ -76,7 +70,7 @@
*
* @param results the recognition results. To retrieve the results in {@code
* ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
- * {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+ * {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
*/
void onResults(Bundle results);
@@ -89,8 +83,15 @@
*
* @param partialResults the returned results. To retrieve the results in
* ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
- * {@link RecognitionManager#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+ * {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
*/
void onPartialResults(Bundle partialResults);
+ /**
+ * Reserved for adding future events.
+ *
+ * @param eventType the type of the occurred event
+ * @param params a Bundle containing the passed parameters
+ */
+ void onEvent(int eventType, Bundle params);
}
diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java
index 79ae480..7915208 100644
--- a/core/java/android/speech/RecognitionManager.java
+++ b/core/java/android/speech/RecognitionManager.java
@@ -22,17 +22,23 @@
import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
/**
* This class provides access to the speech recognition service. This service allows access to the
* speech recognizer. Do not instantiate this class directly, instead, call
- * {@link RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}. This
- * class is not thread safe and must be synchronized externally if accessed from multiple threads.
+ * {@link RecognitionManager#createRecognitionManager(Context)}. This class's methods must be
+ * invoked only from the main application thread. Please note that the application must have
+ * {@link android.Manifest.permission#RECORD_AUDIO} permission to use this class.
*/
public class RecognitionManager {
/** DEBUG value to enable verbose debug prints */
@@ -47,8 +53,40 @@
* {@link RecognitionListener#onPartialResults(Bundle)} methods. These strings are the possible
* recognition results, where the first element is the most likely candidate.
*/
- public static final String RECOGNITION_RESULTS_STRING_ARRAY =
- "recognition_results_string_array";
+ public static final String RESULTS_RECOGNITION = "results_recognition";
+
+ /** Network operation timed out. */
+ public static final int ERROR_NETWORK_TIMEOUT = 1;
+
+ /** Other network related errors. */
+ public static final int ERROR_NETWORK = 2;
+
+ /** Audio recording error. */
+ public static final int ERROR_AUDIO = 3;
+
+ /** Server sends error status. */
+ public static final int ERROR_SERVER = 4;
+
+ /** Other client side errors. */
+ public static final int ERROR_CLIENT = 5;
+
+ /** No speech input */
+ public static final int ERROR_SPEECH_TIMEOUT = 6;
+
+ /** No recognition result matched. */
+ public static final int ERROR_NO_MATCH = 7;
+
+ /** RecognitionService busy. */
+ public static final int ERROR_RECOGNIZER_BUSY = 8;
+
+ /** Insufficient permissions */
+ public static final int ERROR_INSUFFICIENT_PERMISSIONS = 9;
+
+ /** action codes */
+ private final static int MSG_START = 1;
+ private final static int MSG_STOP = 2;
+ private final static int MSG_CANCEL = 3;
+ private final static int MSG_CHANGE_LISTENER = 4;
/** The actual RecognitionService endpoint */
private IRecognitionService mService;
@@ -59,80 +97,74 @@
/** Context with which the manager was created */
private final Context mContext;
- /** Listener that will receive all the callbacks */
- private final RecognitionListener mListener;
-
- /** Helper class wrapping the IRecognitionListener */
- private final InternalRecognitionListener mInternalRecognitionListener;
-
- /** Network operation timed out. */
- public static final int NETWORK_TIMEOUT_ERROR = 1;
-
- /** Other network related errors. */
- public static final int NETWORK_ERROR = 2;
-
- /** Audio recording error. */
- public static final int AUDIO_ERROR = 3;
-
- /** Server sends error status. */
- public static final int SERVER_ERROR = 4;
-
- /** Other client side errors. */
- public static final int CLIENT_ERROR = 5;
-
- /** No speech input */
- public static final int SPEECH_TIMEOUT_ERROR = 6;
-
- /** No recognition result matched. */
- public static final int NO_MATCH_ERROR = 7;
-
- /** RecognitionService busy. */
- public static final int SERVER_BUSY_ERROR = 8;
+ /** Handler that will execute the main tasks */
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START:
+ handleStartListening((Intent) msg.obj);
+ break;
+ case MSG_STOP:
+ handleStopMessage();
+ break;
+ case MSG_CANCEL:
+ handleCancelMessage();
+ break;
+ case MSG_CHANGE_LISTENER:
+ handleChangeListener((RecognitionListener) msg.obj);
+ break;
+ }
+ }
+ };
/**
- * RecognitionManager was not initialized yet, most probably because
- * {@link RecognitionListener#onInit()} was not called yet.
+ * Temporary queue, saving the messages until the connection will be established, afterwards,
+ * only mHandler will receive the messages
*/
- public static final int MANAGER_NOT_INITIALIZED_ERROR = 9;
+ private final Queue<Message> mPendingTasks = new LinkedList<Message>();
+
+ /** The Listener that will receive all the callbacks */
+ private final InternalListener mListener = new InternalListener();
/**
- * The right way to create a RecognitionManager is by using
+ * The right way to create a {@code RecognitionManager} is by using
* {@link #createRecognitionManager} static factory method
*/
- private RecognitionManager(final RecognitionListener listener, final Context context) {
- mInternalRecognitionListener = new InternalRecognitionListener();
+ private RecognitionManager(final Context context) {
mContext = context;
- mListener = listener;
}
/**
- * Basic ServiceConnection which just records mService variable.
+ * Basic ServiceConnection that records the mService variable. Additionally, on creation it
+ * invokes the {@link IRecognitionService#startListening(Intent, IRecognitionListener)}.
*/
private class Connection implements ServiceConnection {
- public synchronized void onServiceConnected(final ComponentName name,
- final IBinder service) {
+ public void onServiceConnected(final ComponentName name, final IBinder service) {
+ // always done on the application main thread, so no need to send message to mHandler
mService = IRecognitionService.Stub.asInterface(service);
- if (mListener != null) {
- mListener.onInit();
- }
if (DBG) Log.d(TAG, "onServiceConnected - Success");
+ while (!mPendingTasks.isEmpty()) {
+ mHandler.sendMessage(mPendingTasks.poll());
+ }
}
public void onServiceDisconnected(final ComponentName name) {
+ // always done on the application main thread, so no need to send message to mHandler
mService = null;
mConnection = null;
+ mPendingTasks.clear();
if (DBG) Log.d(TAG, "onServiceDisconnected - Success");
}
}
/**
* Checks whether a speech recognition service is available on the system. If this method
- * returns {@code false},
- * {@link RecognitionManager#createRecognitionManager(Context, RecognitionListener, Intent)}
- * will fail.
+ * returns {@code false}, {@link RecognitionManager#createRecognitionManager(Context)} will
+ * fail.
*
- * @param context with which RecognitionManager will be created
+ * @param context with which {@code RecognitionManager} will be created
* @return {@code true} if recognition is available, {@code false} otherwise
*/
public static boolean isRecognitionAvailable(final Context context) {
@@ -142,78 +174,60 @@
}
/**
- * Factory method to create a new RecognitionManager
+ * Factory method to create a new {@code RecognitionManager}, please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called before dispatching any
+ * command to the created {@code RecognitionManager}.
*
- * @param context in which to create RecognitionManager
- * @param listener that will receive all the callbacks from the created
- * {@link RecognitionManager}
- * @param recognizerIntent contains initialization parameters for the speech recognizer. The
- * intent action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. Future
- * versions of this API may add startup parameters for speech recognizer.
- * @return null if a recognition service implementation is not installed or if speech
- * recognition is not supported by the device, otherwise a new RecognitionManager is
- * returned. The created RecognitionManager can only be used after the
- * {@link RecognitionListener#onInit()} method has been called.
+ * @param context in which to create {@code RecognitionManager}
+ * @return a new {@code RecognitionManager}
*/
- public static RecognitionManager createRecognitionManager(final Context context,
- final RecognitionListener listener, final Intent recognizerIntent) {
- if (context == null || recognizerIntent == null) {
- throw new IllegalArgumentException(
- "Context and recognizerListener argument cannot be null)");
+ public static RecognitionManager createRecognitionManager(final Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("Context cannot be null)");
}
- RecognitionManager manager = new RecognitionManager(listener, context);
- manager.mConnection = manager.new Connection();
- if (!context.bindService(recognizerIntent, manager.mConnection, Context.BIND_AUTO_CREATE)) {
- Log.e(TAG, "bind to recognition service failed");
- listener.onError(CLIENT_ERROR);
- return null;
- }
- return manager;
+ checkIsCalledFromMainThread();
+ return new RecognitionManager(context);
}
/**
- * Checks whether the service is connected
- *
- * @param functionName from which the call originated
- * @return {@code true} if the service was successfully initialized, {@code false} otherwise
+ * Sets the listener that will receive all the callbacks. The previous unfinished commands will
+ * be executed with the old listener, while any following command will be executed with the new
+ * listener.
+ *
+ * @param listener listener that will receive all the callbacks from the created
+ * {@link RecognitionManager}, this must not be null.
*/
- private boolean connectToService(final String functionName) {
- if (mService != null) {
- return true;
- }
- if (mConnection == null) {
- if (DBG) Log.d(TAG, "restarting connection to the recognition service");
- mConnection = new Connection();
- mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), mConnection,
- Context.BIND_AUTO_CREATE);
- }
- mInternalRecognitionListener.onError(MANAGER_NOT_INITIALIZED_ERROR);
- Log.e(TAG, functionName + " was called before service connection was initialized");
- return false;
+ public void setRecognitionListener(RecognitionListener listener) {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_CHANGE_LISTENER, listener));
}
/**
- * Starts listening for speech.
+ * Starts listening for speech. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
+ * {@link RuntimeException} will be thrown.
*
* @param recognizerIntent contains parameters for the recognition to be performed. The intent
- * action should be {@link RecognizerIntent#ACTION_RECOGNIZE_SPEECH}. The intent may also
- * contain optional extras, see {@link RecognizerIntent}. If these values are not set
- * explicitly, default values will be used by the recognizer.
+ * may also contain optional extras, see {@link RecognizerIntent}. If these values are
+ * not set explicitly, default values will be used by the recognizer.
*/
- public void startListening(Intent recognizerIntent) {
+ public void startListening(final Intent recognizerIntent) {
if (recognizerIntent == null) {
- throw new IllegalArgumentException("recognizerIntent argument cannot be null");
+ throw new IllegalArgumentException("intent must not be null");
}
- if (!connectToService("startListening")) {
- return; // service is not connected yet, reconnect in progress
+ checkIsCalledFromMainThread();
+ if (mConnection == null) { // first time connection
+ mConnection = new Connection();
+ if (!mContext.bindService(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
+ mConnection, Context.BIND_AUTO_CREATE)) {
+ Log.e(TAG, "bind to recognition service failed");
+ mConnection = null;
+ mService = null;
+ mListener.onError(ERROR_CLIENT);
+ return;
+ }
}
- try {
- mService.startListening(recognizerIntent, mInternalRecognitionListener);
- if (DBG) Log.d(TAG, "service start listening command succeded");
- } catch (final RemoteException e) {
- Log.e(TAG, "startListening() failed", e);
- mInternalRecognitionListener.onError(CLIENT_ERROR);
- }
+ putMessage(Message.obtain(mHandler, MSG_START, recognizerIntent));
}
/**
@@ -222,100 +236,200 @@
* called, as the speech endpointer will automatically stop the recognizer listening when it
* determines speech has completed. However, you can manipulate endpointer parameters directly
* using the intent extras defined in {@link RecognizerIntent}, in which case you may sometimes
- * want to manually call this method to stop listening sooner.
+ * want to manually call this method to stop listening sooner. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
+ * {@link RuntimeException} will be thrown.
*/
public void stopListening() {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_STOP));
+ }
+
+ /**
+ * Cancels the speech recognition. Please note that
+ * {@link #setRecognitionListener(RecognitionListener)} must be called beforehand, otherwise a
+ * {@link RuntimeException} will be thrown.
+ */
+ public void cancel() {
+ checkIsCalledFromMainThread();
+ putMessage(Message.obtain(mHandler, MSG_CANCEL));
+ }
+
+ private static void checkIsCalledFromMainThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new RuntimeException(
+ "RecognitionManager should be used only from the application's main thread");
+ }
+ }
+
+ private void putMessage(Message msg) {
if (mService == null) {
- return; // service is not connected, but no need to reconnect at this point
+ mPendingTasks.offer(msg);
+ } else {
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /** sends the actual message to the service */
+ private void handleStartListening(Intent recognizerIntent) {
+ if (!checkOpenConnection()) {
+ return;
}
try {
- mService.stopListening();
+ mService.startListening(recognizerIntent, mListener);
+ if (DBG) Log.d(TAG, "service start listening command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "startListening() failed", e);
+ mListener.onError(ERROR_CLIENT);
+ }
+ }
+
+ /** 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");
} catch (final RemoteException e) {
Log.e(TAG, "stopListening() failed", e);
- mInternalRecognitionListener.onError(CLIENT_ERROR);
+ mListener.onError(ERROR_CLIENT);
}
}
- /**
- * Cancels the speech recognition.
- */
- public void cancel() {
- if (mService == null) {
- return; // service is not connected, but no need to reconnect at this point
+ /** sends the actual message to the service */
+ private void handleCancelMessage() {
+ if (!checkOpenConnection()) {
+ return;
}
try {
- mService.cancel();
+ mService.cancel(mListener);
if (DBG) Log.d(TAG, "service cancel command succeded");
} catch (final RemoteException e) {
Log.e(TAG, "cancel() failed", e);
- mInternalRecognitionListener.onError(CLIENT_ERROR);
+ 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) {
+ if (DBG) Log.d(TAG, "handleChangeListener, listener=" + listener);
+ mListener.mInternalListener = listener;
+ }
/**
- * Destroys the RecognitionManager object. Note that after calling this method all method calls
- * on this object will fail, triggering {@link RecognitionListener#onError}.
+ * Destroys the {@code RecognitionManager} object. Note that after calling this method all
+ * method calls on this object will fail, triggering {@link RecognitionListener#onError}.
*/
public void destroy() {
if (mConnection != null) {
mContext.unbindService(mConnection);
}
+ mPendingTasks.clear();
mService = null;
+ mConnection = null;
}
/**
- * Internal wrapper of IRecognitionListener which will propagate the results
- * to RecognitionListener
+ * Internal wrapper of IRecognitionListener which will propagate the results to
+ * RecognitionListener
*/
- private class InternalRecognitionListener extends IRecognitionListener.Stub {
+ private class InternalListener extends IRecognitionListener.Stub {
+ private RecognitionListener mInternalListener;
+
+ private final static int MSG_BEGINNING_OF_SPEECH = 1;
+ private final static int MSG_BUFFER_RECEIVED = 2;
+ private final static int MSG_END_OF_SPEECH = 3;
+ private final static int MSG_ERROR = 4;
+ private final static int MSG_READY_FOR_SPEECH = 5;
+ private final static int MSG_RESULTS = 6;
+ private final static int MSG_PARTIAL_RESULTS = 7;
+ private final static int MSG_RMS_CHANGED = 8;
+ private final static int MSG_ON_EVENT = 9;
+
+ private final Handler mInternalHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (mInternalListener == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_BEGINNING_OF_SPEECH:
+ mInternalListener.onBeginningOfSpeech();
+ break;
+ case MSG_BUFFER_RECEIVED:
+ mInternalListener.onBufferReceived((byte[]) msg.obj);
+ break;
+ case MSG_END_OF_SPEECH:
+ mInternalListener.onEndOfSpeech();
+ break;
+ case MSG_ERROR:
+ mInternalListener.onError((Integer) msg.obj);
+ break;
+ case MSG_READY_FOR_SPEECH:
+ mInternalListener.onReadyForSpeech((Bundle) msg.obj);
+ break;
+ case MSG_RESULTS:
+ mInternalListener.onResults((Bundle) msg.obj);
+ break;
+ case MSG_PARTIAL_RESULTS:
+ mInternalListener.onPartialResults((Bundle) msg.obj);
+ break;
+ case MSG_RMS_CHANGED:
+ mInternalListener.onRmsChanged((Float) msg.obj);
+ break;
+ case MSG_ON_EVENT:
+ mInternalListener.onEvent(msg.arg1, (Bundle) msg.obj);
+ break;
+ }
+ }
+ };
public void onBeginningOfSpeech() {
- if (mListener != null) {
- mListener.onBeginningOfSpeech();
- }
+ Message.obtain(mInternalHandler, MSG_BEGINNING_OF_SPEECH).sendToTarget();
}
public void onBufferReceived(final byte[] buffer) {
- if (mListener != null) {
- mListener.onBufferReceived(buffer);
- }
+ Message.obtain(mInternalHandler, MSG_BUFFER_RECEIVED, buffer).sendToTarget();
}
public void onEndOfSpeech() {
- if (mListener != null) {
- mListener.onEndOfSpeech();
- }
+ Message.obtain(mInternalHandler, MSG_END_OF_SPEECH).sendToTarget();
}
public void onError(final int error) {
- if (mListener != null) {
- mListener.onError(error);
- }
+ Message.obtain(mInternalHandler, MSG_ERROR, error).sendToTarget();
}
public void onReadyForSpeech(final Bundle noiseParams) {
- if (mListener != null) {
- mListener.onReadyForSpeech(noiseParams);
- }
+ Message.obtain(mInternalHandler, MSG_READY_FOR_SPEECH, noiseParams).sendToTarget();
}
public void onResults(final Bundle results) {
- if (mListener != null) {
- mListener.onResults(results);
- }
+ Message.obtain(mInternalHandler, MSG_RESULTS, results).sendToTarget();
}
public void onPartialResults(final Bundle results) {
- if (mListener != null) {
- mListener.onPartialResults(results);
- }
+ Message.obtain(mInternalHandler, MSG_PARTIAL_RESULTS, results).sendToTarget();
}
public void onRmsChanged(final float rmsdB) {
- if (mListener != null) {
- mListener.onRmsChanged(rmsdB);
- }
+ Message.obtain(mInternalHandler, MSG_RMS_CHANGED, rmsdB).sendToTarget();
+ }
+
+ public void onEvent(final int eventType, final Bundle params) {
+ Message.obtain(mInternalHandler, MSG_ON_EVENT, eventType, eventType, params)
+ .sendToTarget();
}
}
}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
new file mode 100644
index 0000000..0d74960
--- /dev/null
+++ b/core/java/android/speech/RecognitionService.java
@@ -0,0 +1,295 @@
+/*
+ * 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.speech;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class provides a base class for recognition service implementations. This class should be
+ * extended only in case you wish to implement a new speech recognizer. Please not that the
+ * implementation of this service is state-less.
+ */
+public abstract class RecognitionService extends Service {
+
+ /** Log messages identifier */
+ private static final String TAG = "RecognitionService";
+
+ /** Debugging flag */
+ private static final boolean DBG = false;
+
+ /**
+ * The current callback of an application that invoked the
+ * {@link RecognitionService#onStartListening(Intent, Callback)} method
+ */
+ private Callback mCurrentCallback = null;
+
+ private static final int MSG_START_LISTENING = 1;
+
+ private static final int MSG_STOP_LISTENING = 2;
+
+ private static final int MSG_CANCEL = 3;
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_LISTENING:
+ StartListeningArgs args = (StartListeningArgs) msg.obj;
+ dispatchStartListening(args.mIntent, args.mListener);
+ break;
+ case MSG_STOP_LISTENING:
+ dispatchStopListening((IRecognitionListener) msg.obj);
+ break;
+ case MSG_CANCEL:
+ dispatchCancel((IRecognitionListener) msg.obj);
+ }
+ }
+ };
+
+ private void dispatchStartListening(Intent intent, IRecognitionListener listener) {
+ if (mCurrentCallback == null) {
+ if (DBG) Log.d(TAG, "created new mCurrentCallback, listener = " + listener.asBinder());
+ mCurrentCallback = new Callback(listener);
+ RecognitionService.this.onStartListening(intent, mCurrentCallback);
+ } else {
+ try {
+ listener.onError(RecognitionManager.ERROR_RECOGNIZER_BUSY);
+ } catch (RemoteException e) {
+ Log.d(TAG, "onError call from startListening failed");
+ }
+ Log.i(TAG, "concurrent startListening received - ignoring this call");
+ }
+ }
+
+ private void dispatchStopListening(IRecognitionListener listener) {
+ try {
+ if (mCurrentCallback == null) {
+ listener.onError(RecognitionManager.ERROR_CLIENT);
+ Log.w(TAG, "stopListening called with no preceding startListening - ignoring");
+ } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
+ listener.onError(RecognitionManager.ERROR_RECOGNIZER_BUSY);
+ Log.w(TAG, "stopListening called by other caller than startListening - ignoring");
+ } else { // the correct state
+ RecognitionService.this.onStopListening(mCurrentCallback);
+ }
+ } catch (RemoteException e) { // occurs if onError fails
+ Log.d(TAG, "onError call from stopListening failed");
+ }
+ }
+
+ private void dispatchCancel(IRecognitionListener listener) {
+ if (mCurrentCallback == null) {
+ Log.w(TAG, "cancel called with no preceding startListening - ignoring");
+ } else if (mCurrentCallback.mListener.asBinder() != listener.asBinder()) {
+ Log.w(TAG, "cancel called by client who did not call startListening - ignoring");
+ } else { // the correct state
+ RecognitionService.this.onCancel(mCurrentCallback);
+ mCurrentCallback = null;
+ if (DBG) Log.d(TAG, "canceling - setting mCurrentCallback to null");
+ }
+ }
+
+ private class StartListeningArgs {
+ public final Intent mIntent;
+
+ public final IRecognitionListener mListener;
+
+ public StartListeningArgs(Intent intent, IRecognitionListener listener) {
+ this.mIntent = intent;
+ this.mListener = listener;
+ }
+ }
+
+ /** Binder of the recognition service */
+ private final IRecognitionService.Stub mBinder = new IRecognitionService.Stub() {
+ public void startListening(Intent recognizerIntent, IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "startListening called by:" + listener.asBinder());
+ if (checkPermissions(listener)) {
+ mHandler.sendMessage(Message.obtain(mHandler, MSG_START_LISTENING,
+ new StartListeningArgs(recognizerIntent, listener)));
+ }
+ }
+
+ public void stopListening(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "stopListening called by:" + listener.asBinder());
+ if (checkPermissions(listener)) {
+ mHandler.sendMessage(Message.obtain(mHandler, MSG_STOP_LISTENING, listener));
+ }
+ }
+
+ public void cancel(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "cancel called by:" + listener.asBinder());
+ if (checkPermissions(listener)) {
+ mHandler.sendMessage(Message.obtain(mHandler, MSG_CANCEL, listener));
+ }
+ }
+ };
+
+ /**
+ * Checks whether the caller has sufficient permissions
+ *
+ * @param listener to send the error message to in case of error
+ * @return {@code true} if the caller has enough permissions, {@code false} otherwise
+ */
+ private boolean checkPermissions(IRecognitionListener listener) {
+ if (DBG) Log.d(TAG, "checkPermissions");
+ if (RecognitionService.this.checkCallingOrSelfPermission(android.Manifest.permission.
+ RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ try {
+ Log.e(TAG, "call for recognition service without RECORD_AUDIO permissions");
+ listener.onError(RecognitionManager.ERROR_INSUFFICIENT_PERMISSIONS);
+ } catch (RemoteException re) {
+ Log.e(TAG, "sending ERROR_INSUFFICIENT_PERMISSIONS message failed", re);
+ }
+ return false;
+ }
+
+ /**
+ * Notifies the service that it should start listening for speech.
+ *
+ * @param recognizerIntent contains parameters for the recognition to be performed. The intent
+ * may also contain optional extras, see {@link RecognizerIntent}. If these values are
+ * not set explicitly, default values should be used by the recognizer.
+ * @param listener that will receive the service's callbacks
+ */
+ protected abstract void onStartListening(Intent recognizerIntent, Callback listener);
+
+ /**
+ * Notifies the service that it should cancel the speech recognition.
+ */
+ protected abstract void onCancel(Callback listener);
+
+ /**
+ * Notifies the service that it should stop listening for speech. Speech captured so far should
+ * be recognized as if the user had stopped speaking at this point. This method is only called
+ * if the application calls it explicitly.
+ */
+ protected abstract void onStopListening(Callback listener);
+
+ @Override
+ public final IBinder onBind(final Intent intent) {
+ if (DBG) Log.d(TAG, "onBind, intent=" + intent);
+ return mBinder;
+ }
+
+ /**
+ * This class receives callbacks from the speech recognition service and forwards them to the
+ * user. An instance of this class is passed to the
+ * {@link RecognitionService#onStartListening(Intent, Callback)} method. Recognizers may call
+ * these methods on any thread.
+ */
+ public class Callback {
+ private final IRecognitionListener mListener;
+
+ private Callback(IRecognitionListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * The service should call this method when the user has started to speak.
+ */
+ public void beginningOfSpeech() throws RemoteException {
+ if (DBG) Log.d(TAG, "beginningOfSpeech");
+ mListener.onBeginningOfSpeech();
+ }
+
+ /**
+ * The service should call this method when sound has been received. The purpose of this
+ * function is to allow giving feedback to the user regarding the captured audio.
+ *
+ * @param buffer a buffer containing a sequence of big-endian 16-bit integers representing a
+ * single channel audio stream. The sample rate is implementation dependent.
+ */
+ public void bufferReceived(byte[] buffer) throws RemoteException {
+ mListener.onBufferReceived(buffer);
+ }
+
+ /**
+ * The service should call this method after the user stops speaking.
+ */
+ public void endOfSpeech() throws RemoteException {
+ mListener.onEndOfSpeech();
+ }
+
+ /**
+ * The service should call this method when a network or recognition error occurred.
+ *
+ * @param error code is defined in {@link RecognitionManager}
+ */
+ public void error(int error) throws RemoteException {
+ mCurrentCallback = null;
+ mListener.onError(error);
+ }
+
+ /**
+ * The service should call this method when partial recognition results are available. This
+ * method can be called at any time between {@link #beginningOfSpeech()} and
+ * {@link #results(Bundle)} when partial results are ready. This method may be called zero,
+ * one or multiple times for each call to {@link RecognitionManager#startListening(Intent)},
+ * depending on the speech recognition service implementation.
+ *
+ * @param partialResults the returned results. To retrieve the results in
+ * ArrayList<String> format use {@link Bundle#getStringArrayList(String)} with
+ * {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
+ */
+ public void partialResults(Bundle partialResults) throws RemoteException {
+ mListener.onPartialResults(partialResults);
+ }
+
+ /**
+ * The service should call this method when the endpointer is ready for the user to start
+ * speaking.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ public void readyForSpeech(Bundle params) throws RemoteException {
+ mListener.onReadyForSpeech(params);
+ }
+
+ /**
+ * The service should call this method when recognition results are ready.
+ *
+ * @param results the recognition results. To retrieve the results in {@code
+ * ArrayList<String>} format use {@link Bundle#getStringArrayList(String)} with
+ * {@link RecognitionManager#RESULTS_RECOGNITION} as a parameter
+ */
+ public void results(Bundle results) throws RemoteException {
+ mCurrentCallback = null;
+ mListener.onResults(results);
+ }
+
+ /**
+ * The service should call this method when the sound level in the audio stream has changed.
+ * There is no guarantee that this method will be called.
+ *
+ * @param rmsdB the new RMS dB value
+ */
+ public void rmsChanged(float rmsdB) throws RemoteException {
+ mListener.onRmsChanged(rmsdB);
+ }
+ }
+}
diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java
index c8396c4..000e4ce 100644
--- a/core/java/android/view/animation/Animation.java
+++ b/core/java/android/view/animation/Animation.java
@@ -880,7 +880,7 @@
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 98b2594..1546dcd 100644
--- a/core/java/android/view/animation/AnimationSet.java
+++ b/core/java/android/view/animation/AnimationSet.java
@@ -282,7 +282,9 @@
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 66a7631..fd6af05 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -32,7 +32,6 @@
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 @@
* 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 @@
// 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 @@
} 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 @@
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 @@
// 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 @@
}
}
}
-
+
void startSpringback() {
if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
mTouchMode = TOUCH_MODE_OVERFLING;
@@ -2448,19 +2462,33 @@
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 @@
}
}
+
+
+ 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 @@
* @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 @@
incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
}
- final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
+ final int firstPosition = mFirstPosition;
- if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) {
- hideSelector();
- offsetChildrenTopAndBottom(incrementalDeltaY);
- if (!awakenScrollBars()) {
- invalidate();
- }
- mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
- } else {
- 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 + childCount == mItemCount && lastBottom <= end && deltaY < 0) {
- // Don't need to move views up if the bottom of the last position is already visible
- return;
- }
-
- final boolean down = incrementalDeltaY < 0;
-
- hideSelector();
-
- final int headerViewsCount = getHeaderViewsCount();
- final int footerViewsStart = mItemCount - getFooterViewsCount();
-
- 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);
- }
- }
- }
- }
- } 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;
-
- mBlockLayoutRequests = true;
- detachViewsFromParent(start, count);
- offsetChildrenTopAndBottom(incrementalDeltaY);
-
- if (down) {
- mFirstPosition += count;
- }
-
- invalidate();
- fillGap(down);
- mBlockLayoutRequests = false;
-
- invokeOnItemScrollListener();
- awakenScrollBars();
+ 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 true;
+ }
+
+ final boolean down = incrementalDeltaY < 0;
+
+ hideSelector();
+
+ final int headerViewsCount = getHeaderViewsCount();
+ final int footerViewsStart = mItemCount - getFooterViewsCount();
+
+ 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);
+ }
+ }
+ }
+ }
+ } 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;
+
+ mBlockLayoutRequests = true;
+
+ if (count > 0) {
+ detachViewsFromParent(start, count);
+ }
+ offsetChildrenTopAndBottom(incrementalDeltaY);
+
+ if (down) {
+ mFirstPosition += count;
+ }
+
+ invalidate();
+
+ 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 405461a..a4b20da 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -18,14 +18,12 @@
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.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 @@
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 b4e2790..ea5841a 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -360,7 +360,8 @@
// 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 @@
}
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 @@
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 @@
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 @@
// 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 @@
}
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 @@
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 @@
}
}
- 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 @@
// 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 2d36bc8..fd18db4 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -39,6 +39,7 @@
* 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 b90204e..3d09f08 100644
--- a/core/java/com/android/internal/widget/WeightedLinearLayout.java
+++ b/core/java/com/android/internal/widget/WeightedLinearLayout.java
@@ -52,7 +52,8 @@
@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 @@
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1406b66..713e7258 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
new file mode 100644
index 0000000..4bae258
--- /dev/null
+++ b/core/res/res/drawable-hdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_secure.png b/core/res/res/drawable-mdpi/stat_sys_secure.png
new file mode 100644
index 0000000..5f9ae69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/stat_sys_secure.png
Binary files differ
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 231e11c..7ae68f9 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 3dbfa25..cdc15c2 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 d1bfc68..4df570c 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 533338e..1505a7c 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -18,16 +18,23 @@
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 @@
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 @@
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/include/media/MediaProfiles.h b/include/media/MediaProfiles.h
index be928ec..3f253f9 100644
--- a/include/media/MediaProfiles.h
+++ b/include/media/MediaProfiles.h
@@ -53,8 +53,8 @@
*
* Supported param name are:
* file.format - output file format. see mediarecorder.h for details
- * codec.vid - video encoder. see mediarecorder.h for details.
- * codec.aud - audio encoder. see mediarecorder.h for details.
+ * vid.codec - video encoder. see mediarecorder.h for details.
+ * aud.codec - audio encoder. see mediarecorder.h for details.
* vid.width - video frame width
* vid.height - video frame height
* vid.fps - video frame rate
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 0a2fe4c..6041b83 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -28,16 +28,16 @@
* {@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 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 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 @@
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 @@
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 @@
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 2735aa2..bd3113b 100644
--- a/libs/surfaceflinger/LayerBuffer.cpp
+++ b/libs/surfaceflinger/LayerBuffer.cpp
@@ -328,7 +328,8 @@
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 @@
#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;
}
}
- } else {
- err = INVALID_OPERATION;
+ 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();
+ }
+ }
+ }
}
}
#endif
diff --git a/libs/surfaceflinger/LayerBuffer.h b/libs/surfaceflinger/LayerBuffer.h
index e03f92c..3257b76 100644
--- a/libs/surfaceflinger/LayerBuffer.h
+++ b/libs/surfaceflinger/LayerBuffer.h
@@ -145,6 +145,7 @@
mutable LayerBase::Texture mTexture;
mutable NativeBuffer mTempBuffer;
mutable sp<GraphicBuffer> mTempGraphicBuffer;
+ mutable bool mUseEGLImageDirectly;
};
class OverlaySource : public Source {
diff --git a/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java
new file mode 100644
index 0000000..ce56443
--- /dev/null
+++ b/media/java/android/media/CamcorderProfile.java
@@ -0,0 +1,160 @@
+/*
+ * 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.media;
+
+/**
+ * The CamcorderProfile class is used to retrieve the
+ * predefined camcorder profile settings for camcorder applications.
+ * The compressed output from a recording session with a given
+ * CamcorderProfile contains two tracks: one for auido and one for video.
+ *
+ * <p>Each profile specifies the following set of parameters:
+ * <ul>
+ * <li> The file output format, @see android.media.MediaRecorder.OutputFormat
+ * <li> Video codec format, @see android.media.MediaRecorder.VideoEncoder
+ * <li> Video bit rate in bits per second
+ * <li> Video frame rate in frames per second
+ * <li> Video frame width and height,
+ * <li> Audio codec format, @see android.media.MediaRecorder.AudioEncoder
+ * <li> Audio bit rate in bits per second,
+ * <li> Audio sample rate
+ * <li> Number of audio channels for recording.
+ * </ul>
+ * {@hide}
+ */
+public class CamcorderProfile
+{
+
+ /**
+ * The Quality class represents the quality level of each CamcorderProfile.
+ *
+ * The output from recording sessions with high quality level usually may have
+ * larger output bit rate, better video and/or audio recording quality, and
+ * laerger video frame resolution and higher audio sampling rate, etc, than those
+ * with low quality level.
+ */
+ public enum Quality {
+ /* Do not change these values/ordinals without updating their counterpart
+ * in include/media/MediaProfiles.h!
+ */
+ HIGH,
+ LOW
+ };
+
+ /**
+ * The quality level of the camcorder profile
+ * @see android.media.CamcorderProfile.Quality
+ */
+ public final Quality mQuality;
+
+ /**
+ * The file output format of the camcorder profile
+ * @see android.media.MediaRecorder.OutputFormat
+ */
+ public final int mFileFormat;
+
+ /**
+ * The video encoder being used for the video track
+ * @see android.media.MediaRecorder.VideoEncoder
+ */
+ public final int mVideoCodec;
+
+ /**
+ * The target video output bit rate in bits per second
+ */
+ public final int mVideoBitRate;
+
+ /**
+ * The target video frame rate in frames per second
+ */
+ public final int mVideoFrameRate;
+
+ /**
+ * The target video frame width in pixels
+ */
+ public final int mVideoFrameWidth;
+
+ /**
+ * The target video frame height in pixels
+ */
+ public final int mVideoFrameHeight;
+
+ /**
+ * The audio encoder being used for the audio track.
+ * @see android.media.MediaRecorder.AudioEncoder
+ */
+ public final int mAudioCodec;
+
+ /**
+ * The target audio output bit rate in bits per second
+ */
+ public final int mAudioBitRate;
+
+ /**
+ * The audio sampling rate used for the audio track
+ */
+ public final int mAudioSampleRate;
+
+ /**
+ * The number of audio channels used for the audio track
+ */
+ public final int mAudioChannels;
+
+ /**
+ * Returns the camcorder profile for the given quality.
+ * @param quality the target quality level for the camcorder profile
+ * @see android.media.CamcorderProfile.Quality
+ */
+ public static CamcorderProfile get(Quality quality) {
+ return native_get_camcorder_profile(quality.ordinal());
+ }
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+
+ // Private constructor called by JNI
+ private CamcorderProfile(int quality,
+ int fileFormat,
+ int videoCodec,
+ int videoBitRate,
+ int videoFrameRate,
+ int videoWidth,
+ int videoHeight,
+ int audioCodec,
+ int audioBitRate,
+ int audioSampleRate,
+ int audioChannels) {
+
+ mQuality = Quality.values()[quality];
+ mFileFormat = fileFormat;
+ mVideoCodec = videoCodec;
+ mVideoBitRate = videoBitRate;
+ mVideoFrameRate = videoFrameRate;
+ mVideoFrameWidth = videoWidth;
+ mVideoFrameHeight = videoHeight;
+ mAudioCodec = audioCodec;
+ mAudioBitRate = audioBitRate;
+ mAudioSampleRate = audioSampleRate;
+ mAudioChannels = audioChannels;
+ }
+
+ // Methods implemented by JNI
+ private static native final void native_init();
+ private static native final CamcorderProfile native_get_camcorder_profile(int quality);
+}
diff --git a/media/jni/android_media_MediaProfiles.cpp b/media/jni/android_media_MediaProfiles.cpp
index cd3ad88..50380c1 100644
--- a/media/jni/android_media_MediaProfiles.cpp
+++ b/media/jni/android_media_MediaProfiles.cpp
@@ -162,7 +162,54 @@
return cap;
}
-static JNINativeMethod gMethods[] = {
+static jobject
+android_media_MediaProfiles_native_get_camcorder_profile(JNIEnv *env, jobject thiz, jint quality)
+{
+ LOGV("native_get_camcorder_profile: %d", quality);
+ if (quality != CAMCORDER_QUALITY_HIGH && quality != CAMCORDER_QUALITY_LOW) {
+ jniThrowException(env, "java/lang/RuntimeException", "Unknown camcorder profile quality");
+ return NULL;
+ }
+
+ camcorder_quality q = static_cast<camcorder_quality>(quality);
+ int fileFormat = sProfiles->getCamcorderProfileParamByName("file.format", q);
+ int videoCodec = sProfiles->getCamcorderProfileParamByName("vid.codec", q);
+ int videoBitRate = sProfiles->getCamcorderProfileParamByName("vid.bps", q);
+ int videoFrameRate = sProfiles->getCamcorderProfileParamByName("vid.fps", q);
+ int videoFrameWidth = sProfiles->getCamcorderProfileParamByName("vid.width", q);
+ int videoFrameHeight = sProfiles->getCamcorderProfileParamByName("vid.height", q);
+ int audioCodec = sProfiles->getCamcorderProfileParamByName("aud.codec", q);
+ int audioBitRate = sProfiles->getCamcorderProfileParamByName("aud.bps", q);
+ int audioSampleRate = sProfiles->getCamcorderProfileParamByName("aud.hz", q);
+ int audioChannels = sProfiles->getCamcorderProfileParamByName("aud.ch", q);
+
+ // Check on the values retrieved
+ if (fileFormat == -1 || videoCodec == -1 || audioCodec == -1 ||
+ videoBitRate == -1 || videoFrameRate == -1 || videoFrameWidth == -1 || videoFrameHeight == -1 ||
+ audioBitRate == -1 || audioSampleRate == -1 || audioChannels == -1) {
+
+ jniThrowException(env, "java/lang/RuntimeException", "Error retrieving camcorder profile params");
+ return NULL;
+ }
+
+ jclass camcorderProfileClazz = env->FindClass("android/media/CamcorderProfile");
+ jmethodID camcorderProfileConstructorMethodID = env->GetMethodID(camcorderProfileClazz, "<init>", "(IIIIIIIIIII)V");
+ return env->NewObject(camcorderProfileClazz,
+ camcorderProfileConstructorMethodID,
+ quality,
+ fileFormat,
+ videoCodec,
+ videoBitRate,
+ videoFrameRate,
+ videoFrameWidth,
+ videoFrameHeight,
+ audioCodec,
+ audioBitRate,
+ audioSampleRate,
+ audioChannels);
+}
+
+static JNINativeMethod gMethodsForEncoderCapabilitiesClass[] = {
{"native_init", "()V", (void *)android_media_MediaProfiles_native_init},
{"native_get_num_file_formats", "()I", (void *)android_media_MediaProfiles_native_get_num_file_formats},
{"native_get_file_format", "(I)I", (void *)android_media_MediaProfiles_native_get_file_format},
@@ -176,12 +223,29 @@
(void *)android_media_MediaProfiles_native_get_audio_encoder_cap},
};
-static const char* const kClassPathName = "android/media/MediaProfiles";
+static JNINativeMethod gMethodsForCamcorderProfileClass[] = {
+ {"native_init", "()V", (void *)android_media_MediaProfiles_native_init},
+ {"native_get_camcorder_profile", "(I)Landroid/media/CamcorderProfile;",
+ (void *)android_media_MediaProfiles_native_get_camcorder_profile},
+};
+
+static const char* const kEncoderCapabilitiesClassPathName = "android/media/EncoderCapabilities";
+static const char* const kCamcorderProfileClassPathName = "android/media/CamcorderProfile";
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaProfiles(JNIEnv *env)
{
- return AndroidRuntime::registerNativeMethods(env,
- "android/media/EncoderCapabilities", gMethods, NELEM(gMethods));
+ int ret1 = AndroidRuntime::registerNativeMethods(env,
+ kEncoderCapabilitiesClassPathName,
+ gMethodsForEncoderCapabilitiesClass,
+ NELEM(gMethodsForEncoderCapabilitiesClass));
+
+ int ret2 = AndroidRuntime::registerNativeMethods(env,
+ kCamcorderProfileClassPathName,
+ gMethodsForCamcorderProfileClass,
+ NELEM(gMethodsForCamcorderProfileClass));
+
+ // Success if ret1 == 0 && ret2 == 0
+ return (ret1 || ret2);
}
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
index 51fcaf5..c05d90a 100644
--- a/media/libstagefright/omx/tests/OMXHarness.cpp
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -512,7 +512,9 @@
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 @@
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 81864bd..b6e0aae 100644
--- a/opengl/libagl/egl.cpp
+++ b/opengl/libagl/egl.cpp
@@ -2092,7 +2092,20 @@
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 a1a776f..fa25fa9 100644
--- a/opengl/libagl/texture.cpp
+++ b/opengl/libagl/texture.cpp
@@ -1628,6 +1628,11 @@
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 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 d2f8ced..145e25e 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -166,7 +166,8 @@
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 @@
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 @@
}
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 @@
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 @@
// 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 1efa5a3..b7eea2e 100755
--- a/packages/TtsService/src/android/tts/TtsService.java
+++ b/packages/TtsService/src/android/tts/TtsService.java
@@ -802,8 +802,8 @@
}
if (synthAvailable) {
synthesizerLock.unlock();
+ processSpeechQueue();
}
- processSpeechQueue();
}
}
}
@@ -882,8 +882,8 @@
}
if (synthAvailable) {
synthesizerLock.unlock();
+ processSpeechQueue();
}
- processSpeechQueue();
}
}
}
diff --git a/preloaded-classes b/preloaded-classes
index 3e2eb13..092b539 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -22,14 +22,14 @@
android.app.ActivityThread$ProviderRefCount
android.app.AlertDialog
android.app.Application
-android.app.ApplicationContext
-android.app.ApplicationContext$ApplicationContentResolver
-android.app.ApplicationContext$ApplicationPackageManager
-android.app.ApplicationContext$ApplicationPackageManager$PackageRemovedReceiver
-android.app.ApplicationContext$ApplicationPackageManager$ResourceName
-android.app.ApplicationContext$SharedPreferencesImpl
android.app.ApplicationLoaders
android.app.ApplicationThreadNative
+android.app.ContextImpl
+android.app.ContextImpl$ApplicationContentResolver
+android.app.ContextImpl$ApplicationPackageManager
+android.app.ContextImpl$ApplicationPackageManager$PackageRemovedReceiver
+android.app.ContextImpl$ApplicationPackageManager$ResourceName
+android.app.ContextImpl$SharedPreferencesImpl
android.app.Dialog
android.app.ExpandableListActivity
android.app.IActivityManager
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index f330651..c28cf44 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -242,6 +242,14 @@
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 6795bdd..2f845e1 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -258,8 +258,11 @@
// 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 @@
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 e14a973..1e7dd99 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -212,11 +212,13 @@
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 @@
}
}
+ 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 @@
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 @@
// 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 @@
}
};
+ 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 962b2f9..69c6d2d 100644
--- a/test-runner/android/test/TouchUtils.java
+++ b/test-runner/android/test/TouchUtils.java
@@ -773,7 +773,7 @@
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 @@
}
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 bff8fba..0d29c35 100644
--- a/tests/AndroidTests/Android.mk
+++ b/tests/AndroidTests/Android.mk
@@ -5,8 +5,6 @@
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 5f4ab78..1da59d1 100644
--- a/tests/CoreTests/android/content/SyncQueueTest.java
+++ b/tests/CoreTests/android/content/SyncQueueTest.java
@@ -66,22 +66,22 @@
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 @@
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);
}