Merge "Turn the debugging back on."
diff --git a/api/current.xml b/api/current.xml
index 527ffe3..03e20fa4 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -40253,6 +40253,16 @@
visibility="public"
>
</field>
+<field name="delayUntil"
+ type="long"
+ transient="false"
+ volatile="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="fullSyncRequested"
type="boolean"
transient="false"
@@ -55200,6 +55210,88 @@
>
</field>
</class>
+<class name="GestureUtilities"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="computeOrientedBoundingBox"
+ return="android.gesture.OrientedBoundingBox"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="originalPoints" type="java.util.ArrayList<android.gesture.GesturePoint>">
+</parameter>
+</method>
+<method name="computeOrientedBoundingBox"
+ return="android.gesture.OrientedBoundingBox"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="originalPoints" type="float[]">
+</parameter>
+</method>
+<method name="spatialSampling"
+ return="float[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="gesture" type="android.gesture.Gesture">
+</parameter>
+<parameter name="bitmapSize" type="int">
+</parameter>
+</method>
+<method name="spatialSampling"
+ return="float[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="gesture" type="android.gesture.Gesture">
+</parameter>
+<parameter name="bitmapSize" type="int">
+</parameter>
+<parameter name="keepAspectRatio" type="boolean">
+</parameter>
+</method>
+<method name="temporalSampling"
+ return="float[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="stroke" type="android.gesture.GestureStroke">
+</parameter>
+<parameter name="numPoints" type="int">
+</parameter>
+</method>
+</class>
<class name="OrientedBoundingBox"
extends="java.lang.Object"
abstract="false"
@@ -130602,6 +130694,17 @@
visibility="public"
>
</field>
+<field name="ACTION_DEVICE_INFO_SETTINGS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.settings.DEVICE_INFO_SETTINGS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="ACTION_DISPLAY_SETTINGS"
type="java.lang.String"
transient="false"
@@ -134065,6 +134168,320 @@
</package>
<package name="android.speech"
>
+<interface name="RecognitionListener"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onBeginningOfSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onBufferReceived"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="buffer" type="byte[]">
+</parameter>
+</method>
+<method name="onEndOfSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onError"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="error" type="int">
+</parameter>
+</method>
+<method name="onInit"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="onPartialResults"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="partialResults" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onReadyForSpeech"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="params" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onResults"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="results" type="android.os.Bundle">
+</parameter>
+</method>
+<method name="onRmsChanged"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rmsdB" type="float">
+</parameter>
+</method>
+</interface>
+<class name="RecognitionManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="cancel"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="createRecognitionManager"
+ return="android.speech.RecognitionManager"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<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"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isRecognitionAvailable"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</method>
+<method name="startListening"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="recognizerIntent" type="android.content.Intent">
+</parameter>
+</method>
+<method name="stopListening"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<field name="AUDIO_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="CLIENT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="5"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MANAGER_NOT_INITIALIZED_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="9"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NETWORK_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NETWORK_TIMEOUT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NO_MATCH_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="7"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ 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"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SERVER_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SPEECH_TIMEOUT_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="6"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
<class name="RecognizerIntent"
extends="java.lang.Object"
abstract="false"
@@ -134084,6 +134501,17 @@
visibility="public"
>
</field>
+<field name="ACTION_VOICE_SEARCH_SETTINGS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.action.VOICE_SEARCH_SETTINGS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="ACTION_WEB_SEARCH"
type="java.lang.String"
transient="false"
@@ -134172,6 +134600,39 @@
visibility="public"
>
</field>
+<field name="EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="LANGUAGE_MODEL_FREE_FORM"
type="java.lang.String"
transient="false"
diff --git a/cmds/stagefright/audioloop.cpp b/cmds/stagefright/audioloop.cpp
index 3788e73..8733662 100644
--- a/cmds/stagefright/audioloop.cpp
+++ b/cmds/stagefright/audioloop.cpp
@@ -1,7 +1,10 @@
#include "SineSource.h"
#include <binder/ProcessState.h>
+#include <media/mediarecorder.h>
+#include <media/stagefright/AMRWriter.h>
#include <media/stagefright/AudioPlayer.h>
+#include <media/stagefright/AudioSource.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
@@ -21,7 +24,16 @@
OMXClient client;
CHECK_EQ(client.connect(), OK);
+#if 0
sp<MediaSource> source = new SineSource(kSampleRate, kNumChannels);
+#else
+ sp<MediaSource> source = new AudioSource(
+ AUDIO_SOURCE_DEFAULT,
+ kSampleRate,
+ kNumChannels == 1
+ ? AudioSystem::CHANNEL_IN_MONO
+ : AudioSystem::CHANNEL_IN_STEREO);
+#endif
sp<MetaData> meta = new MetaData;
@@ -43,12 +55,19 @@
meta, true /* createEncoder */,
source);
+#if 1
+ sp<AMRWriter> writer = new AMRWriter("/sdcard/out.amr");
+ writer->addSource(encoder);
+ writer->start();
+ sleep(10);
+ writer->stop();
+#else
sp<MediaSource> decoder = OMXCodec::Create(
client.interface(),
meta, false /* createEncoder */,
encoder);
-#if 1
+#if 0
AudioPlayer *player = new AudioPlayer(NULL);
player->setSource(decoder);
@@ -60,7 +79,7 @@
delete player;
player = NULL;
-#else
+#elif 0
CHECK_EQ(decoder->start(), OK);
MediaBuffer *buffer;
@@ -76,6 +95,7 @@
CHECK_EQ(decoder->stop(), OK);
#endif
+#endif
return 0;
}
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index ee26d3c..e3ccd00 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -506,7 +506,7 @@
+ ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
}
}
-
+
public void removeAccount(IAccountManagerResponse response, Account account) {
checkManageAccountsPermission();
long identityToken = clearCallingIdentity();
@@ -1660,9 +1660,16 @@
}
}
} else {
+ Account[] accounts = getAccounts(null /* type */);
+ fout.println("Accounts: " + accounts.length);
+ for (Account account : accounts) {
+ fout.println(" " + account);
+ }
+
+ fout.println();
synchronized (mSessions) {
final long now = SystemClock.elapsedRealtime();
- fout.println("AccountManagerService: " + mSessions.size() + " sessions");
+ fout.println("Active Sessions: " + mSessions.size());
for (Session session : mSessions.values()) {
fout.println(" " + session.toDebugString(now));
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d76b616..321ba5c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1278,6 +1278,17 @@
/**
* Use with {@link #getSystemService} to retrieve a {@link
+ * android.net.NetworkManagementService} for handling management of
+ * system network services
+ *
+ * @hide
+ * @see #getSystemService
+ * @see android.net.NetworkManagementService
+ */
+ public static final String NETWORKMANAGEMENT_SERVICE = "network_management";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a {@link
* android.net.wifi.WifiManager} for handling management of
* Wi-Fi access.
*
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index a9c61dc..c9077bc 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,8 +16,6 @@
package android.content;
-import com.google.android.collect.Maps;
-
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
@@ -29,7 +27,6 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.RegisteredServicesCache;
@@ -45,15 +42,14 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.text.format.Time;
-import android.util.Config;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -65,12 +61,8 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
-import java.util.Map;
-import java.util.PriorityQueue;
import java.util.Random;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
@@ -78,7 +70,7 @@
/**
* @hide
*/
-class SyncManager implements OnAccountsUpdateListener {
+public class SyncManager implements OnAccountsUpdateListener {
private static final String TAG = "SyncManager";
// used during dumping of the Sync history
@@ -144,9 +136,6 @@
private Context mContext;
- private String mStatusText = "";
- private long mHeartbeatTime = 0;
-
private volatile Account[] mAccounts = null;
volatile private PowerManager.WakeLock mSyncWakeLock;
@@ -156,12 +145,9 @@
private final NotificationManager mNotificationMgr;
private AlarmManager mAlarmService = null;
- private HandlerThread mSyncThread;
-
- private volatile IPackageManager mPackageManager;
private final SyncStorageEngine mSyncStorageEngine;
- private final SyncQueue mSyncQueue;
+ public final SyncQueue mSyncQueue;
private ActiveSyncContext mActiveSyncContext = null;
@@ -312,8 +298,6 @@
private static final String SYNCMANAGER_PREFS_FILENAME = "/data/system/syncmanager.prefs";
- private final boolean mFactoryTest;
-
private volatile boolean mBootCompleted = false;
private ConnectivityManager getConnectivityManager() {
@@ -327,8 +311,6 @@
}
public SyncManager(Context context, boolean factoryTest) {
- mFactoryTest = factoryTest;
-
// Initialize the SyncStorageEngine first, before registering observers
// and creating threads and so on; it may fail if the disk is full.
SyncStorageEngine.init(context);
@@ -337,11 +319,10 @@
mContext = context;
- mSyncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
- mSyncThread.start();
- mSyncHandler = new SyncHandler(mSyncThread.getLooper());
-
- mPackageManager = null;
+ HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ syncThread.start();
+ mSyncHandler = new SyncHandler(syncThread.getLooper());
mSyncAdapters = new SyncAdaptersCache(mContext);
@@ -635,16 +616,12 @@
if (isLoggable) {
Log.v(TAG, "not syncing because sync is disabled");
}
- setStatusText("Sync is disabled.");
return;
}
final boolean backgroundDataUsageAllowed = !mBootCompleted ||
getConnectivityManager().getBackgroundDataSetting();
- if (!mDataConnectionIsConnected) setStatusText("No data connection");
- if (mStorageIsLow) setStatusText("Memory low");
-
if (extras == null) extras = new Bundle();
Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
@@ -670,7 +647,6 @@
if (isLoggable) {
Log.v(TAG, "scheduleSync: no accounts configured, dropping");
}
- setStatusText("No accounts are configured.");
return;
}
}
@@ -759,10 +735,6 @@
}
}
- private void setStatusText(String message) {
- mStatusText = message;
- }
-
public void scheduleLocalSync(Account account, String authority) {
final Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
@@ -770,17 +742,6 @@
false /* onlyThoseWithUnkownSyncableState */);
}
- private IPackageManager getPackageManager() {
- // Don't bother synchronizing on this. The worst that can happen is that two threads
- // can try to get the package manager at the same time but only one result gets
- // used. Since there is only one package manager in the system this doesn't matter.
- if (mPackageManager == null) {
- IBinder b = ServiceManager.getService("package");
- mPackageManager = IPackageManager.Stub.asInterface(b);
- }
- return mPackageManager;
- }
-
public SyncAdapterType[] getSyncAdapterTypes() {
final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
mSyncAdapters.getAllServices();
@@ -793,11 +754,6 @@
return types;
}
- public void updateHeartbeatTime() {
- mHeartbeatTime = SystemClock.elapsedRealtime();
- mSyncStorageEngine.reportActiveChange();
- }
-
private void sendSyncAlarmMessage() {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
@@ -840,22 +796,24 @@
}
}
- private void rescheduleImmediately(SyncOperation syncOperation) {
- SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
- rescheduledSyncOperation.setDelay(0);
- scheduleSyncOperation(rescheduledSyncOperation);
+ private void clearBackoffSetting(SyncOperation op) {
+ mSyncStorageEngine.setBackoff(op.account, op.authority,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
}
- private long rescheduleWithDelay(SyncOperation syncOperation) {
- long newDelayInMs;
+ private void increaseBackoffSetting(SyncOperation op) {
+ final long now = SystemClock.elapsedRealtime();
- if (syncOperation.delay <= 0) {
+ final Pair<Long, Long> previousSettings =
+ mSyncStorageEngine.getBackoff(op.account, op.authority);
+ long newDelayInMs;
+ if (previousSettings == null || previousSettings.second <= 0) {
// The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
(long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
} else {
// Subsequent delays are the double of the previous delay
- newDelayInMs = syncOperation.delay * 2;
+ newDelayInMs = previousSettings.second * 2;
}
// Cap the delay
@@ -866,10 +824,20 @@
newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
}
- SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
- rescheduledSyncOperation.setDelay(newDelayInMs);
- scheduleSyncOperation(rescheduledSyncOperation);
- return newDelayInMs;
+ mSyncStorageEngine.setBackoff(op.account, op.authority,
+ now + newDelayInMs, newDelayInMs);
+ }
+
+ private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
+ final long delayUntil = delayUntilSeconds * 1000;
+ final long absoluteNow = System.currentTimeMillis();
+ long newDelayUntilTime;
+ if (delayUntil > absoluteNow) {
+ newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
+ } else {
+ newDelayUntilTime = 0;
+ }
+ mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
}
/**
@@ -905,28 +873,26 @@
public void scheduleSyncOperation(SyncOperation syncOperation) {
// If this operation is expedited and there is a sync in progress then
// reschedule the current operation and send a cancel for it.
- final boolean expedited = syncOperation.delay < 0;
final ActiveSyncContext activeSyncContext = mActiveSyncContext;
- if (expedited && activeSyncContext != null) {
- final boolean activeIsExpedited = activeSyncContext.mSyncOperation.delay < 0;
+ if (syncOperation.expedited && activeSyncContext != null) {
final boolean hasSameKey =
activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
// This request is expedited and there is a sync in progress.
// Interrupt the current sync only if it is not expedited and if it has a different
// key than the one we are scheduling.
- if (!activeIsExpedited && !hasSameKey) {
- rescheduleImmediately(activeSyncContext.mSyncOperation);
+ if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
+ scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
sendSyncFinishedOrCanceledMessage(activeSyncContext,
null /* no result since this is a cancel */);
}
}
- boolean operationEnqueued;
+ boolean queueChanged;
synchronized (mSyncQueue) {
- operationEnqueued = mSyncQueue.add(syncOperation);
+ queueChanged = mSyncQueue.add(syncOperation);
}
- if (operationEnqueued) {
+ if (queueChanged) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
}
@@ -945,16 +911,17 @@
* @param authority limit the removals to operations with this authority, if non-null
*/
public void clearScheduledSyncOperations(Account account, String authority) {
+ mSyncStorageEngine.setBackoff(account, authority,
+ SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
synchronized (mSyncQueue) {
mSyncQueue.clear(account, authority);
}
}
- void maybeRescheduleSync(SyncResult syncResult, SyncOperation previousSyncOperation) {
+ void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
if (isLoggable) {
- Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", "
- + previousSyncOperation);
+ Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
}
// If this sync aborted because the internal sync loop retried too many times then
@@ -963,119 +930,33 @@
// If this was a two-way sync then retry soft errors with an exponential backoff.
// If this was an upward sync then schedule a two-way sync immediately.
// Otherwise do not reschedule.
- if (syncResult.tooManyRetries) {
+ if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
+ Log.d(TAG, "not retrying sync operation because it is a manual sync: "
+ + operation);
+ } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
+ final SyncOperation newSyncOperation = new SyncOperation(operation);
+ newSyncOperation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
+ Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+ + "encountered an error: " + operation);
+ scheduleSyncOperation(newSyncOperation);
+ } else if (syncResult.tooManyRetries) {
Log.d(TAG, "not retrying sync operation because it retried too many times: "
- + previousSyncOperation);
+ + operation);
} else if (syncResult.madeSomeProgress()) {
if (isLoggable) {
- Log.d(TAG, "retrying sync operation immediately because "
- + "even though it had an error it achieved some success");
+ Log.d(TAG, "retrying sync operation because even though it had an error "
+ + "it achieved some success");
}
- rescheduleImmediately(previousSyncOperation);
- } else if (previousSyncOperation.extras.getBoolean(
- ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
- final SyncOperation newSyncOperation = new SyncOperation(previousSyncOperation);
- newSyncOperation.extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
- newSyncOperation.setDelay(0);
- if (Config.LOGD) {
- Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
- + "encountered an error: " + previousSyncOperation);
- }
- scheduleSyncOperation(newSyncOperation);
+ scheduleSyncOperation(new SyncOperation(operation));
} else if (syncResult.hasSoftError()) {
- long delay = rescheduleWithDelay(previousSyncOperation);
- if (delay >= 0) {
- if (isLoggable) {
- Log.d(TAG, "retrying sync operation in " + delay + " ms because "
- + "it encountered a soft error: " + previousSyncOperation);
- }
+ if (isLoggable) {
+ Log.d(TAG, "retrying sync operation because it encountered a soft error: "
+ + operation);
}
+ scheduleSyncOperation(new SyncOperation(operation));
} else {
- if (Config.LOGD) {
- Log.d(TAG, "not retrying sync operation because the error is a hard error: "
- + previousSyncOperation);
- }
- }
- }
-
- /**
- * Value type that represents a sync operation.
- */
- static class SyncOperation implements Comparable {
- final Account account;
- int syncSource;
- String authority;
- Bundle extras;
- final String key;
- long earliestRunTime;
- long delay;
- SyncStorageEngine.PendingOperation pendingOperation;
-
- SyncOperation(Account account, int source, String authority, Bundle extras, long delay) {
- this.account = account;
- this.syncSource = source;
- this.authority = authority;
- this.extras = new Bundle(extras);
- this.setDelay(delay);
- this.key = toKey();
- }
-
- SyncOperation(SyncOperation other) {
- this.account = other.account;
- this.syncSource = other.syncSource;
- this.authority = other.authority;
- this.extras = new Bundle(other.extras);
- this.delay = other.delay;
- this.earliestRunTime = other.earliestRunTime;
- this.key = toKey();
- }
-
- public void setDelay(long delay) {
- this.delay = delay;
- if (delay >= 0) {
- this.earliestRunTime = SystemClock.elapsedRealtime() + delay;
- } else {
- this.earliestRunTime = 0;
- }
- }
-
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account: ").append(account);
- sb.append(" extras: ");
- extrasToStringBuilder(extras, sb);
- sb.append(" syncSource: ").append(syncSource);
- sb.append(" when: ").append(earliestRunTime);
- sb.append(" delay: ").append(delay);
- sb.append(" key: {").append(key).append("}");
- if (pendingOperation != null) sb.append(" pendingOperation: ").append(pendingOperation);
- return sb.toString();
- }
-
- private String toKey() {
- StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account: ").append(account);
- sb.append(" extras: ");
- extrasToStringBuilder(extras, sb);
- return sb.toString();
- }
-
- private static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
- sb.append("[");
- for (String key : bundle.keySet()) {
- sb.append(key).append("=").append(bundle.get(key)).append(" ");
- }
- sb.append("]");
- }
-
- public int compareTo(Object o) {
- SyncOperation other = (SyncOperation)o;
- if (earliestRunTime == other.earliestRunTime) {
- return 0;
- }
- return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+ Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+ + operation);
}
}
@@ -1100,10 +981,7 @@
}
public void sendHeartbeat() {
- // ignore this call if it corresponds to an old sync session
- if (mActiveSyncContext == this) {
- SyncManager.this.updateHeartbeatTime();
- }
+ // heartbeats are no longer used
}
public void onFinished(SyncResult result) {
@@ -1255,7 +1133,8 @@
pw.print(" #"); pw.print(i); pw.print(": account=");
pw.print(op.account.name); pw.print(":");
pw.print(op.account.type); pw.print(" authority=");
- pw.println(op.authority);
+ pw.print(op.authority); pw.print(" expedited=");
+ pw.println(op.expedited);
if (op.extras != null && op.extras.size() > 0) {
sb.setLength(0);
SyncOperation.extrasToStringBuilder(op.extras, sb);
@@ -1295,6 +1174,24 @@
}
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);
@@ -1568,11 +1465,9 @@
}
SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
if (mActiveSyncContext != payload.activeSyncContext) {
- if (Config.LOGD) {
- Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
- + "dropping: mActiveSyncContext " + mActiveSyncContext
- + " != " + payload.activeSyncContext);
- }
+ Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
+ + "dropping: mActiveSyncContext " + mActiveSyncContext
+ + " != " + payload.activeSyncContext);
return;
}
runSyncFinishedOrCanceled(payload.syncResult);
@@ -1685,14 +1580,12 @@
if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
SyncOperation nextSyncOperation;
synchronized (mSyncQueue) {
- nextSyncOperation = mSyncQueue.head();
+ nextSyncOperation = mSyncQueue.nextReadyToRun(now);
}
- if (nextSyncOperation != null && nextSyncOperation.earliestRunTime <= now) {
- if (Config.LOGD) {
- Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
- + activeSyncContext.mSyncOperation);
- }
- rescheduleImmediately(activeSyncContext.mSyncOperation);
+ if (nextSyncOperation != null) {
+ Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
+ + activeSyncContext.mSyncOperation);
+ scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
sendSyncFinishedOrCanceledMessage(activeSyncContext,
null /* no result since this is a cancel */);
} else {
@@ -1712,7 +1605,6 @@
if (isLoggable) {
Log.v(TAG, "runStateIdle: no data connection, skipping");
}
- setStatusText("No data connection");
return;
}
@@ -1720,7 +1612,6 @@
if (isLoggable) {
Log.v(TAG, "runStateIdle: memory low, skipping");
}
- setStatusText("Memory low");
return;
}
@@ -1731,7 +1622,6 @@
if (isLoggable) {
Log.v(TAG, "runStateIdle: accounts not known, skipping");
}
- setStatusText("Accounts not known yet");
return;
}
@@ -1742,8 +1632,9 @@
final boolean backgroundDataUsageAllowed =
getConnectivityManager().getBackgroundDataSetting();
synchronized (mSyncQueue) {
+ final long now = SystemClock.elapsedRealtime();
while (true) {
- op = mSyncQueue.head();
+ op = mSyncQueue.nextReadyToRun(now);
if (op == null) {
if (isLoggable) {
Log.v(TAG, "runStateIdle: no more sync operations, returning");
@@ -1751,73 +1642,57 @@
return;
}
+ // we are either going to run this sync or drop it so go ahead and remove it
+ // 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);
}
- mSyncQueue.popHead();
- 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();
- boolean syncAllowed =
- manualSync || (backgroundDataUsageAllowed && syncAutomatically);
- int isSyncable = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
- if (isSyncable == 0) {
- // if not syncable, don't allow
- syncAllowed = false;
- } else if (isSyncable < 0) {
- // if the syncable state is unknown, only allow initialization syncs
- syncAllowed =
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- }
- if (!syncAllowed) {
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: sync off, dropping " + op);
- }
- mSyncQueue.popHead();
continue;
}
// skip the sync if the account of this operation no longer exists
if (!ArrayUtils.contains(accounts, op.account)) {
- mSyncQueue.popHead();
if (isLoggable) {
Log.v(TAG, "runStateIdle: account not present, dropping " + op);
}
continue;
}
- // go ahead and try to sync this syncOperation
- if (isLoggable) {
- Log.v(TAG, "runStateIdle: found sync candidate: " + op);
+ // 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);
+ }
+ continue;
+ }
+
+ // go ahead and try to sync this syncOperation
break;
}
- // If the first SyncOperation isn't ready to run schedule a wakeup and
- // get out.
- final long now = SystemClock.elapsedRealtime();
- if (op.earliestRunTime > now) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "runStateIdle: the time is " + now + " yet the next "
- + "sync operation is for " + op.earliestRunTime + ": " + op);
- }
- return;
- }
-
- // We will do this sync. Remove it from the queue and run it outside of the
- // synchronized block.
+ // We will do this sync. Run it outside of the synchronized block.
if (isLoggable) {
Log.v(TAG, "runStateIdle: we are going to sync " + op);
}
- mSyncQueue.popHead();
}
// connect to the sync adapter
@@ -1825,9 +1700,7 @@
RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
mSyncAdapters.getServiceInfo(syncAdapterType);
if (syncAdapterInfo == null) {
- if (Config.LOGD) {
- Log.d(TAG, "can't find a sync adapter for " + syncAdapterType);
- }
+ Log.d(TAG, "can't find a sync adapter for " + syncAdapterType);
runStateIdle();
return;
}
@@ -1861,25 +1734,22 @@
syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
syncOperation.account, syncOperation.extras);
} catch (RemoteException remoteExc) {
- if (Config.LOGD) {
- Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
- }
+ Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
mActiveSyncContext.unBindFromSyncAdapter();
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
- rescheduleWithDelay(syncOperation);
+ increaseBackoffSetting(syncOperation);
+ scheduleSyncOperation(new SyncOperation(syncOperation));
} catch (RuntimeException exc) {
mActiveSyncContext.unBindFromSyncAdapter();
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
- Log.e(TAG, "Caught a RuntimeException while starting the sync " + syncOperation,
- exc);
+ Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
}
}
private void runSyncFinishedOrCanceled(SyncResult syncResult) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (isLoggable) Log.v(TAG, "runSyncFinishedOrCanceled");
final ActiveSyncContext activeSyncContext = mActiveSyncContext;
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
@@ -1893,32 +1763,34 @@
int upstreamActivity;
if (syncResult != null) {
if (isLoggable) {
- Log.v(TAG, "runSyncFinishedOrCanceled: is a finished: operation "
+ Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
+ syncOperation + ", result " + syncResult);
}
if (!syncResult.hasError()) {
- if (isLoggable) {
- Log.v(TAG, "finished sync operation " + syncOperation);
- }
historyMessage = SyncStorageEngine.MESG_SUCCESS;
// TODO: set these correctly when the SyncResult is extended to include it
downstreamActivity = 0;
upstreamActivity = 0;
+ clearBackoffSetting(syncOperation);
} else {
- maybeRescheduleSync(syncResult, syncOperation);
- if (Config.LOGD) {
- Log.d(TAG, "failed sync operation " + syncOperation);
+ Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
+ // the operation failed so increase the backoff time
+ if (!syncResult.syncAlreadyInProgress) {
+ increaseBackoffSetting(syncOperation);
}
+ // reschedule the sync if so indicated by the syncResult
+ maybeRescheduleSync(syncResult, syncOperation);
historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
// TODO: set these correctly when the SyncResult is extended to include it
downstreamActivity = 0;
upstreamActivity = 0;
}
+
+ setDelayUntilTime(syncOperation, syncResult.delayUntil);
} else {
if (isLoggable) {
- Log.v(TAG, "runSyncFinishedOrCanceled: is a cancel: operation "
- + syncOperation);
+ Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
}
if (activeSyncContext.mSyncAdapter != null) {
try {
@@ -2070,19 +1942,17 @@
if (mAccounts == null) return;
if (mStorageIsLow) return;
+ final long now = SystemClock.elapsedRealtime();
+
// Compute the alarm fire time:
// - not syncing: time of the next sync operation
// - syncing, no notification: time from sync start to notification create time
// - syncing, with notification: time till timeout of the active sync operation
- Long alarmTime = null;
+ Long alarmTime;
ActiveSyncContext activeSyncContext = mActiveSyncContext;
if (activeSyncContext == null) {
- SyncOperation syncOperation;
synchronized (mSyncQueue) {
- syncOperation = mSyncQueue.head();
- }
- if (syncOperation != null) {
- alarmTime = syncOperation.earliestRunTime;
+ alarmTime = mSyncQueue.nextRunTime(now);
}
} else {
final long notificationTime =
@@ -2104,7 +1974,7 @@
when += ERROR_NOTIFICATION_DELAY_MS;
// convert when fron absolute time to elapsed run time
long delay = when - System.currentTimeMillis();
- when = SystemClock.elapsedRealtime() + delay;
+ when = now + delay;
alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
}
}
@@ -2228,221 +2098,4 @@
}
}
- static class SyncQueue {
- private SyncStorageEngine mSyncStorageEngine;
-
- private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false;
-
- // A priority queue of scheduled SyncOperations that is designed to make it quick
- // to find the next SyncOperation that should be considered for running.
- private final PriorityQueue<SyncOperation> mOpsByWhen = new PriorityQueue<SyncOperation>();
-
- // A Map of SyncOperations operationKey -> SyncOperation that is designed for
- // quick lookup of an enqueued SyncOperation.
- private final HashMap<String, SyncOperation> mOpsByKey = Maps.newHashMap();
-
- public SyncQueue(SyncStorageEngine syncStorageEngine) {
- mSyncStorageEngine = syncStorageEngine;
- ArrayList<SyncStorageEngine.PendingOperation> ops
- = mSyncStorageEngine.getPendingOperations();
- final int N = ops.size();
- for (int i=0; i<N; i++) {
- SyncStorageEngine.PendingOperation op = ops.get(i);
- SyncOperation syncOperation = new SyncOperation(
- op.account, op.syncSource, op.authority, op.extras, 0);
- syncOperation.pendingOperation = op;
- add(syncOperation, op);
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- }
-
- public boolean add(SyncOperation operation) {
- return add(new SyncOperation(operation),
- null /* this is not coming from the database */);
- }
-
- private boolean add(SyncOperation operation,
- SyncStorageEngine.PendingOperation pop) {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
-
- // If this operation is expedited then set its earliestRunTime to be immediately
- // before the head of the list, or not if none are in the list.
- if (operation.delay < 0) {
- SyncOperation headOperation = head();
- if (headOperation != null) {
- operation.earliestRunTime = Math.min(SystemClock.elapsedRealtime(),
- headOperation.earliestRunTime - 1);
- } else {
- operation.earliestRunTime = SystemClock.elapsedRealtime();
- }
- }
-
- // - if an operation with the same key exists and this one should run earlier,
- // delete the old one and add the new one
- // - if an operation with the same key exists and if this one should run
- // later, ignore it
- // - if no operation exists then add the new one
- final String operationKey = operation.key;
- SyncOperation existingOperation = mOpsByKey.get(operationKey);
-
- // if this operation matches an existing operation that is being retried (delay > 0)
- // and this isn't a manual sync operation, ignore this operation
- if (existingOperation != null && existingOperation.delay > 0) {
- if (!operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
- return false;
- }
- }
-
- if (existingOperation != null
- && operation.earliestRunTime >= existingOperation.earliestRunTime) {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
- return false;
- }
-
- if (existingOperation != null) {
- removeByKey(operationKey);
- }
-
- operation.pendingOperation = pop;
- if (operation.pendingOperation == null) {
- pop = new SyncStorageEngine.PendingOperation(
- operation.account, operation.syncSource,
- operation.authority, operation.extras);
- pop = mSyncStorageEngine.insertIntoPending(pop);
- if (pop == null) {
- throw new IllegalStateException("error adding pending sync operation "
- + operation);
- }
- operation.pendingOperation = pop;
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) {
- debugCheckDataStructures(
- false /* don't compare with the DB, since we know
- it is inconsistent right now */ );
- }
- mOpsByKey.put(operationKey, operation);
- mOpsByWhen.add(operation);
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
- return true;
- }
-
- public void removeByKey(String operationKey) {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- SyncOperation operationToRemove = mOpsByKey.remove(operationKey);
- if (!mOpsByWhen.remove(operationToRemove)) {
- throw new IllegalStateException(
- "unable to find " + operationToRemove + " in mOpsByWhen");
- }
-
- if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + operationToRemove;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- }
-
- public SyncOperation head() {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- return mOpsByWhen.peek();
- }
-
- public void popHead() {
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- SyncOperation operation = mOpsByWhen.remove();
- if (mOpsByKey.remove(operation.key) == null) {
- throw new IllegalStateException("unable to find " + operation + " in mOpsByKey");
- }
-
- if (!mSyncStorageEngine.deleteFromPending(operation.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + operation;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- }
-
- public void clear(Account account, String authority) {
- Iterator<Map.Entry<String, SyncOperation>> entries = mOpsByKey.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 (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- entries.remove();
- if (!mOpsByWhen.remove(syncOperation)) {
- throw new IllegalStateException(
- "unable to find " + syncOperation + " in mOpsByWhen");
- }
-
- if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + syncOperation;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
-
- if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
- }
- }
-
- public void dump(StringBuilder sb) {
- sb.append("SyncQueue: ").append(mOpsByWhen.size()).append(" operation(s)\n");
- for (SyncOperation operation : mOpsByWhen) {
- sb.append(operation).append("\n");
- }
- }
-
- private void debugCheckDataStructures(boolean checkDatabase) {
- if (mOpsByKey.size() != mOpsByWhen.size()) {
- throw new IllegalStateException("size mismatch: "
- + mOpsByKey .size() + " != " + mOpsByWhen.size());
- }
- for (SyncOperation operation : mOpsByWhen) {
- if (!mOpsByKey.containsKey(operation.key)) {
- throw new IllegalStateException(
- "operation " + operation + " is in mOpsByWhen but not mOpsByKey");
- }
- }
- for (Map.Entry<String, SyncOperation> entry : mOpsByKey.entrySet()) {
- final SyncOperation operation = entry.getValue();
- final String key = entry.getKey();
- if (!key.equals(operation.key)) {
- throw new IllegalStateException(
- "operation " + operation + " in mOpsByKey doesn't match key " + key);
- }
- if (!mOpsByWhen.contains(operation)) {
- throw new IllegalStateException(
- "operation " + operation + " is in mOpsByKey but not mOpsByWhen");
- }
- }
-
- if (checkDatabase) {
- final int N = mSyncStorageEngine.getPendingOperationCount();
- if (mOpsByKey.size() != N) {
- ArrayList<SyncStorageEngine.PendingOperation> ops
- = mSyncStorageEngine.getPendingOperations();
- StringBuilder sb = new StringBuilder();
- for (int i=0; i<N; i++) {
- SyncStorageEngine.PendingOperation op = ops.get(i);
- sb.append("#");
- sb.append(i);
- sb.append(": account=");
- sb.append(op.account);
- sb.append(" syncSource=");
- sb.append(op.syncSource);
- sb.append(" authority=");
- sb.append(op.authority);
- sb.append("\n");
- }
- dump(sb);
- throw new IllegalStateException("DB size mismatch: "
- + mOpsByKey.size() + " != " + N + "\n"
- + sb.toString());
- }
- }
- }
- }
}
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
new file mode 100644
index 0000000..2d6e833
--- /dev/null
+++ b/core/java/android/content/SyncOperation.java
@@ -0,0 +1,95 @@
+package android.content;
+
+import android.accounts.Account;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+/**
+ * Value type that represents a sync operation.
+ * @hide
+ */
+public class SyncOperation implements Comparable {
+ public final Account account;
+ public int syncSource;
+ public String authority;
+ public Bundle extras;
+ public final String key;
+ public long earliestRunTime;
+ public boolean expedited;
+ public SyncStorageEngine.PendingOperation pendingOperation;
+
+ public SyncOperation(Account account, int source, String authority, Bundle extras,
+ long delay) {
+ this.account = account;
+ this.syncSource = source;
+ this.authority = authority;
+ this.extras = new Bundle(extras);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
+ removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+ final long now = SystemClock.elapsedRealtime();
+ if (delay < 0) {
+ this.expedited = true;
+ this.earliestRunTime = now;
+ } else {
+ this.expedited = false;
+ this.earliestRunTime = now + delay;
+ }
+ this.key = toKey();
+ }
+
+ private void removeFalseExtra(String extraName) {
+ if (!extras.getBoolean(extraName, false)) {
+ extras.remove(extraName);
+ }
+ }
+
+ SyncOperation(SyncOperation other) {
+ this.account = other.account;
+ this.syncSource = other.syncSource;
+ this.authority = other.authority;
+ this.extras = new Bundle(other.extras);
+ this.expedited = other.expedited;
+ this.earliestRunTime = SystemClock.elapsedRealtime();
+ this.key = toKey();
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("authority: ").append(authority);
+ sb.append(" account: ").append(account);
+ sb.append(" extras: ");
+ extrasToStringBuilder(extras, sb);
+ sb.append(" syncSource: ").append(syncSource);
+ sb.append(" when: ").append(earliestRunTime);
+ sb.append(" expedited: ").append(expedited);
+ return sb.toString();
+ }
+
+ private String toKey() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("authority: ").append(authority);
+ sb.append(" account: ").append(account);
+ sb.append(" extras: ");
+ extrasToStringBuilder(extras, sb);
+ return sb.toString();
+ }
+
+ public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+ sb.append("[");
+ for (String key : bundle.keySet()) {
+ sb.append(key).append("=").append(bundle.get(key)).append(" ");
+ }
+ sb.append("]");
+ }
+
+ public int compareTo(Object o) {
+ SyncOperation other = (SyncOperation)o;
+ if (earliestRunTime == other.earliestRunTime) {
+ return 0;
+ }
+ return (earliestRunTime < other.earliestRunTime) ? -1 : 1;
+ }
+}
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
new file mode 100644
index 0000000..a9f15d9
--- /dev/null
+++ b/core/java/android/content/SyncQueue.java
@@ -0,0 +1,190 @@
+package android.content;
+
+import com.google.android.collect.Maps;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Pair;
+import android.util.Log;
+import android.accounts.Account;
+
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Iterator;
+
+/**
+ *
+ * @hide
+ */
+public class SyncQueue {
+ private static final String TAG = "SyncManager";
+ private SyncStorageEngine mSyncStorageEngine;
+
+ // A Map of SyncOperations operationKey -> SyncOperation that is designed for
+ // quick lookup of an enqueued SyncOperation.
+ private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
+
+ public SyncQueue(SyncStorageEngine syncStorageEngine) {
+ mSyncStorageEngine = syncStorageEngine;
+ ArrayList<SyncStorageEngine.PendingOperation> ops
+ = mSyncStorageEngine.getPendingOperations();
+ 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);
+ syncOperation.pendingOperation = op;
+ add(syncOperation, op);
+ }
+ }
+
+ public boolean add(SyncOperation operation) {
+ return add(operation, null /* this is not coming from the database */);
+ }
+
+ private boolean add(SyncOperation operation,
+ SyncStorageEngine.PendingOperation pop) {
+ // - if an operation with the same key exists and this one should run earlier,
+ // update the earliestRunTime of the existing to the new time
+ // - if an operation with the same key exists and if this one should run
+ // later, ignore it
+ // - if no operation exists then add the new one
+ final String operationKey = operation.key;
+ final SyncOperation existingOperation = mOperationsMap.get(operationKey);
+
+ if (existingOperation != null) {
+ boolean changed = false;
+ if (existingOperation.expedited == operation.expedited) {
+ final long newRunTime =
+ Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
+ if (existingOperation.earliestRunTime != newRunTime) {
+ existingOperation.earliestRunTime = newRunTime;
+ changed = true;
+ }
+ } else {
+ if (operation.expedited) {
+ existingOperation.expedited = true;
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ operation.pendingOperation = pop;
+ if (operation.pendingOperation == null) {
+ pop = new SyncStorageEngine.PendingOperation(
+ operation.account, operation.syncSource,
+ operation.authority, operation.extras, operation.expedited);
+ pop = mSyncStorageEngine.insertIntoPending(pop);
+ if (pop == null) {
+ throw new IllegalStateException("error adding pending sync operation "
+ + operation);
+ }
+ operation.pendingOperation = pop;
+ }
+
+ mOperationsMap.put(operationKey, operation);
+ return true;
+ }
+
+ public void remove(SyncOperation operation) {
+ SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+ if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
+ final String errorMessage = "unable to find pending row for " + operationToRemove;
+ Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+ }
+ }
+
+ /**
+ * 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;
+ 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;
+ }
+ }
+ if (lowestOp == null) {
+ return null;
+ }
+ return Pair.create(lowestOp, lowestOpRunTime);
+ }
+
+ /**
+ * Return when the next SyncOperation will be ready to run or null if there are
+ * none.
+ * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
+ * decide if the sync operation is ready to run
+ * @return when the next SyncOperation will be ready to run, expressed in elapsedRealtime()
+ */
+ public Long nextRunTime(long now) {
+ Pair<SyncOperation, Long> nextOpAndRunTime = nextOperation(now);
+ if (nextOpAndRunTime == null) {
+ return null;
+ }
+ return nextOpAndRunTime.second;
+ }
+
+ /**
+ * Find and return the SyncOperation that should be run next and is ready to run.
+ * @param now the current {@link android.os.SystemClock#elapsedRealtime()}, used to
+ * 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);
+ if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
+ return null;
+ }
+ return nextOpAndRunTime.first;
+ }
+
+ public void clear(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;
+ entries.remove();
+ if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
+ final String errorMessage = "unable to find pending row for " + syncOperation;
+ Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+ }
+ }
+ }
+
+ public void dump(StringBuilder sb) {
+ sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
+ for (SyncOperation operation : mOperationsMap.values()) {
+ sb.append(operation).append("\n");
+ }
+ }
+}
diff --git a/core/java/android/content/SyncResult.java b/core/java/android/content/SyncResult.java
index 57161b6..3fbe847 100644
--- a/core/java/android/content/SyncResult.java
+++ b/core/java/android/content/SyncResult.java
@@ -14,6 +14,9 @@
public boolean fullSyncRequested;
public boolean partialSyncUnavailable;
public boolean moreRecordsToGet;
+
+ // in seconds since epoch
+ public long delayUntil;
public final SyncStats stats;
public static final SyncResult ALREADY_IN_PROGRESS;
@@ -32,6 +35,7 @@
this.fullSyncRequested = false;
this.partialSyncUnavailable = false;
this.moreRecordsToGet = false;
+ this.delayUntil = 0;
this.stats = new SyncStats();
}
@@ -43,6 +47,7 @@
fullSyncRequested = parcel.readInt() != 0;
partialSyncUnavailable = parcel.readInt() != 0;
moreRecordsToGet = parcel.readInt() != 0;
+ delayUntil = parcel.readLong();
stats = new SyncStats(parcel);
}
@@ -80,6 +85,7 @@
fullSyncRequested = false;
partialSyncUnavailable = false;
moreRecordsToGet = false;
+ delayUntil = 0;
stats.clear();
}
@@ -105,6 +111,7 @@
parcel.writeInt(fullSyncRequested ? 1 : 0);
parcel.writeInt(partialSyncUnavailable ? 1 : 0);
parcel.writeInt(moreRecordsToGet ? 1 : 0);
+ parcel.writeLong(delayUntil);
stats.writeToParcel(parcel, flags);
}
@@ -123,6 +130,7 @@
sb.append(" partialSyncUnavailable: ").append(partialSyncUnavailable);
}
if (moreRecordsToGet) sb.append(" moreRecordsToGet: ").append(moreRecordsToGet);
+ if (delayUntil > 0) sb.append(" delayUntil: ").append(delayUntil);
sb.append(stats);
return sb.toString();
}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 4c53201..db70096 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -36,10 +36,11 @@
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.SystemProperties;
+import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
+import android.util.Pair;
import java.io.File;
import java.io.FileInputStream;
@@ -88,6 +89,8 @@
/** Enum value for a user-initiated sync. */
public static final int SOURCE_USER = 3;
+ public static final long NOT_IN_BACKOFF_MODE = -1;
+
private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
@@ -117,16 +120,18 @@
final int syncSource;
final String authority;
final Bundle extras; // note: read-only.
+ final boolean expedited;
int authorityId;
byte[] flatExtras;
PendingOperation(Account account, int source,
- String authority, Bundle extras) {
+ String authority, Bundle extras, boolean expedited) {
this.account = account;
this.syncSource = source;
this.authority = authority;
this.extras = extras != null ? new Bundle(extras) : extras;
+ this.expedited = expedited;
this.authorityId = -1;
}
@@ -136,6 +141,7 @@
this.authority = other.authority;
this.extras = other.extras;
this.authorityId = other.authorityId;
+ this.expedited = other.expedited;
}
}
@@ -155,6 +161,9 @@
final int ident;
boolean enabled;
int syncable;
+ long backoffTime;
+ long backoffDelay;
+ long delayUntil;
AuthorityInfo(Account account, String authority, int ident) {
this.account = account;
@@ -162,6 +171,8 @@
this.ident = ident;
enabled = SYNC_ENABLED_DEFAULT;
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
}
}
@@ -280,7 +291,7 @@
public static void init(Context context) {
if (sSyncStorageEngine != null) {
- throw new IllegalStateException("already initialized");
+ return;
}
sSyncStorageEngine = new SyncStorageEngine(context);
}
@@ -380,7 +391,7 @@
}
if (!wasEnabled && sync) {
- mContext.getContentResolver().requestSync(account, providerName, new Bundle());
+ ContentResolver.requestSync(account, providerName, new Bundle());
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
@@ -424,11 +435,91 @@
}
if (oldState <= 0 && syncable > 0) {
- mContext.getContentResolver().requestSync(account, providerName, new Bundle());
+ ContentResolver.requestSync(account, providerName, new Bundle());
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
+ public Pair<Long, Long> getBackoff(Account account, String providerName) {
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, providerName, "getBackoff");
+ if (authority == null || authority.backoffTime < 0) {
+ return null;
+ }
+ return Pair.create(authority.backoffTime, authority.backoffDelay);
+ }
+ }
+
+ public void setBackoff(Account account, String providerName,
+ long nextSyncTime, long nextDelay) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
+ + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
+ }
+ boolean changed = false;
+ synchronized (mAuthorities) {
+ if (account == null || providerName == null) {
+ for (AccountInfo accountInfo : mAccounts.values()) {
+ if (account != null && !account.equals(accountInfo.account)) continue;
+ for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+ if (providerName != null && !providerName.equals(authorityInfo.authority)) {
+ continue;
+ }
+ if (authorityInfo.backoffTime != nextSyncTime
+ || authorityInfo.backoffDelay != nextDelay) {
+ authorityInfo.backoffTime = nextSyncTime;
+ authorityInfo.backoffDelay = nextDelay;
+ changed = true;
+ }
+ }
+ }
+ } else {
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(account, providerName, -1, false);
+ if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
+ return;
+ }
+ authority.backoffTime = nextSyncTime;
+ authority.backoffDelay = nextDelay;
+ changed = true;
+ }
+ if (changed) {
+ writeAccountInfoLocked();
+ }
+ }
+
+ if (changed) {
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+ }
+
+ public void setDelayUntilTime(Account account, String providerName, long delayUntil) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
+ + " -> delayUntil " + delayUntil);
+ }
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+ if (authority.delayUntil == delayUntil) {
+ return;
+ }
+ authority.delayUntil = delayUntil;
+ writeAccountInfoLocked();
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public long getDelayUntilTime(Account account, String providerName) {
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, providerName, "getDelayUntil");
+ if (authority == null) {
+ return 0;
+ }
+ return authority.delayUntil;
+ }
+ }
+
public void setMasterSyncAutomatically(boolean flag) {
boolean old;
synchronized (mAuthorities) {
@@ -437,7 +528,7 @@
writeAccountInfoLocked();
}
if (!old && flag) {
- mContext.getContentResolver().requestSync(null, null, new Bundle());
+ ContentResolver.requestSync(null, null, new Bundle());
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
@@ -512,9 +603,6 @@
SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
status.pending = true;
- status.initialize = op.extras != null &&
- op.extras.containsKey(ContentResolver.SYNC_EXTRAS_INITIALIZE) &&
- op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE);
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
@@ -1414,7 +1502,7 @@
}
}
- public static final int PENDING_OPERATION_VERSION = 1;
+ public static final int PENDING_OPERATION_VERSION = 2;
/**
* Read all pending operations back in to the initial engine state.
@@ -1429,7 +1517,7 @@
final int SIZE = in.dataSize();
while (in.dataPosition() < SIZE) {
int version = in.readInt();
- if (version != PENDING_OPERATION_VERSION) {
+ if (version != PENDING_OPERATION_VERSION && version != 1) {
Log.w(TAG, "Unknown pending operation version "
+ version + "; dropping all ops");
break;
@@ -1437,6 +1525,12 @@
int authorityId = in.readInt();
int syncSource = in.readInt();
byte[] flatExtras = in.createByteArray();
+ boolean expedited;
+ if (version == PENDING_OPERATION_VERSION) {
+ expedited = in.readInt() != 0;
+ } else {
+ expedited = false;
+ }
AuthorityInfo authority = mAuthorities.get(authorityId);
if (authority != null) {
Bundle extras = null;
@@ -1445,12 +1539,13 @@
}
PendingOperation op = new PendingOperation(
authority.account, syncSource,
- authority.authority, extras);
+ authority.authority, extras, expedited);
op.authorityId = authorityId;
op.flatExtras = flatExtras;
if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
+ " auth=" + op.authority
+ " src=" + op.syncSource
+ + " expedited=" + op.expedited
+ " extras=" + op.extras);
mPendingOperations.add(op);
}
@@ -1468,6 +1563,7 @@
op.flatExtras = flattenBundle(op.extras);
}
out.writeByteArray(op.flatExtras);
+ out.writeInt(op.expedited ? 1 : 0);
}
/**
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index a7ea507..808c839 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -234,6 +234,14 @@
public static final int FLAG_ON_SDCARD = 1<<19;
/**
+ * Value for {@link #flags}: Set to true if the application is
+ * native-debuggable, i.e. embeds a gdbserver binary in its .apk
+ *
+ * {@hide}
+ */
+ public static final int FLAG_NATIVE_DEBUGGABLE = 1<<20;
+
+ /**
* Flags associated with the application. Any combination of
* {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE},
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
diff --git a/core/java/android/gesture/GestureStroke.java b/core/java/android/gesture/GestureStroke.java
index 68dc5a6..c3ddb28 100644
--- a/core/java/android/gesture/GestureStroke.java
+++ b/core/java/android/gesture/GestureStroke.java
@@ -31,7 +31,7 @@
* consists of a sequence of timed points. One or multiple strokes form a gesture.
*/
public class GestureStroke {
- static final float TOUCH_TOLERANCE = 8;
+ static final float TOUCH_TOLERANCE = 3;
public final RectF boundingBox;
diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtilities.java
index dfe1d00..9d95ce4 100755
--- a/core/java/android/gesture/GestureUtilities.java
+++ b/core/java/android/gesture/GestureUtilities.java
@@ -26,7 +26,17 @@
import static android.gesture.GestureConstants.*;
-final class GestureUtilities {
+/**
+ * Utility functions for gesture processing & analysis, including methods for:
+ * <ul>
+ * <li>feature extraction (e.g., samplers and those for calculating bounding
+ * boxes and gesture path lengths);
+ * <li>geometric transformation (e.g., translation, rotation and scaling);
+ * <li>gesture similarity comparison (e.g., calculating Euclidean or Cosine
+ * distances between two gestures).
+ * </ul>
+ */
+public final class GestureUtilities {
private static final float SCALING_THRESHOLD = 0.26f;
private static final float NONUNIFORM_SCALE = (float) Math.sqrt(2);
@@ -49,14 +59,38 @@
}
}
- static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension) {
- return spatialSampling(gesture, sampleMatrixDimension, false);
+ /**
+ * Samples the gesture spatially by rendering the gesture into a 2D
+ * grayscale bitmap. Scales the gesture to fit the size of the bitmap.
+ * The scaling does not necessarily keep the aspect ratio of the gesture.
+ *
+ * @param gesture the gesture to be sampled
+ * @param bitmapSize the size of the bitmap
+ * @return a bitmapSize x bitmapSize grayscale bitmap that is represented
+ * as a 1D array. The float at index i represents the grayscale
+ * value at pixel [i%bitmapSize, i/bitmapSize]
+ */
+ public static float[] spatialSampling(Gesture gesture, int bitmapSize) {
+ return spatialSampling(gesture, bitmapSize, false);
}
- static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension,
- boolean uniformScaling) {
- final float targetPatchSize = sampleMatrixDimension - 1; // edge inclusive
- float[] sample = new float[sampleMatrixDimension * sampleMatrixDimension];
+ /**
+ * Samples the gesture spatially by rendering the gesture into a 2D
+ * grayscale bitmap. Scales the gesture to fit the size of the bitmap.
+ *
+ * @param gesture the gesture to be sampled
+ * @param bitmapSize the size of the bitmap
+ * @param keepAspectRatio if the scaling should keep the gesture's
+ * aspect ratio
+ *
+ * @return a bitmapSize x bitmapSize grayscale bitmap that is represented
+ * as a 1D array. The float at index i represents the grayscale
+ * value at pixel [i%bitmapSize, i/bitmapSize]
+ */
+ public static float[] spatialSampling(Gesture gesture, int bitmapSize,
+ boolean keepAspectRatio) {
+ final float targetPatchSize = bitmapSize - 1;
+ float[] sample = new float[bitmapSize * bitmapSize];
Arrays.fill(sample, 0);
RectF rect = gesture.getBoundingBox();
@@ -65,7 +99,7 @@
float sx = targetPatchSize / gestureWidth;
float sy = targetPatchSize / gestureHeight;
- if (uniformScaling) {
+ if (keepAspectRatio) {
float scale = sx < sy ? sx : sy;
sx = scale;
sy = scale;
@@ -122,16 +156,16 @@
if (segmentStartY > targetPatchSize) {
segmentStartY = targetPatchSize;
}
- plot(segmentStartX, segmentStartY, sample, sampleMatrixDimension);
+ plot(segmentStartX, segmentStartY, sample, bitmapSize);
if (segmentEndX != -1) {
- // evaluate horizontally
+ // Evaluate horizontally
if (segmentEndX > segmentStartX) {
xpos = (float) Math.ceil(segmentStartX);
float slope = (segmentEndY - segmentStartY) /
(segmentEndX - segmentStartX);
while (xpos < segmentEndX) {
ypos = slope * (xpos - segmentStartX) + segmentStartY;
- plot(xpos, ypos, sample, sampleMatrixDimension);
+ plot(xpos, ypos, sample, bitmapSize);
xpos++;
}
} else if (segmentEndX < segmentStartX){
@@ -140,18 +174,18 @@
(segmentEndX - segmentStartX);
while (xpos < segmentStartX) {
ypos = slope * (xpos - segmentStartX) + segmentStartY;
- plot(xpos, ypos, sample, sampleMatrixDimension);
+ plot(xpos, ypos, sample, bitmapSize);
xpos++;
}
}
- // evaluating vertically
+ // Evaluate vertically
if (segmentEndY > segmentStartY) {
ypos = (float) Math.ceil(segmentStartY);
float invertSlope = (segmentEndX - segmentStartX) /
(segmentEndY - segmentStartY);
while (ypos < segmentEndY) {
xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
- plot(xpos, ypos, sample, sampleMatrixDimension);
+ plot(xpos, ypos, sample, bitmapSize);
ypos++;
}
} else if (segmentEndY < segmentStartY) {
@@ -160,7 +194,7 @@
(segmentEndY - segmentStartY);
while (ypos < segmentStartY) {
xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
- plot(xpos, ypos, sample, sampleMatrixDimension);
+ plot(xpos, ypos, sample, bitmapSize);
ypos++;
}
}
@@ -224,15 +258,16 @@
}
/**
- * Featurizes a stroke into a vector of a given number of elements
+ * Samples a stroke temporally into a given number of evenly-distributed
+ * points.
*
- * @param stroke
- * @param sampleSize
- * @return a float array
+ * @param stroke the gesture stroke to be sampled
+ * @param numPoints the number of points
+ * @return the sampled points in the form of [x1, y1, x2, y2, ..., xn, yn]
*/
- static float[] temporalSampling(GestureStroke stroke, int sampleSize) {
- final float increment = stroke.length / (sampleSize - 1);
- int vectorLength = sampleSize * 2;
+ public static float[] temporalSampling(GestureStroke stroke, int numPoints) {
+ final float increment = stroke.length / (numPoints - 1);
+ int vectorLength = numPoints * 2;
float[] vector = new float[vectorLength];
float distanceSoFar = 0;
float[] pts = stroke.points;
@@ -287,9 +322,9 @@
}
/**
- * Calculate the centroid
+ * Calculates the centroid of a set of points.
*
- * @param points
+ * @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn]
* @return the centroid
*/
static float[] computeCentroid(float[] points) {
@@ -309,10 +344,10 @@
}
/**
- * calculate the variance-covariance matrix, treat each point as a sample
+ * Calculates the variance-covariance matrix of a set of points.
*
- * @param points
- * @return the covariance matrix
+ * @param points the points in the form of [x1, y1, x2, y2, ..., xn, yn]
+ * @return the variance-covariance matrix
*/
private static float[][] computeCoVariance(float[] points) {
float[][] array = new float[2][2];
@@ -363,7 +398,7 @@
}
/**
- * Calculate the squared Euclidean distance between two vectors
+ * Calculates the squared Euclidean distance between two vectors.
*
* @param vector1
* @param vector2
@@ -380,7 +415,7 @@
}
/**
- * Calculate the cosine distance between two instances
+ * Calculates the cosine distance between two instances.
*
* @param vector1
* @param vector2
@@ -396,7 +431,7 @@
}
/**
- * Calculate the "minimum" cosine distance between two instances
+ * Calculates the "minimum" cosine distance between two instances.
*
* @param vector1
* @param vector2
@@ -426,8 +461,13 @@
}
}
-
- static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> originalPoints) {
+ /**
+ * Computes an oriented, minimum bounding box of a set of points.
+ *
+ * @param originalPoints
+ * @return an oriented bounding box
+ */
+ public static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> originalPoints) {
final int count = originalPoints.size();
float[] points = new float[count * 2];
for (int i = 0; i < count; i++) {
@@ -440,7 +480,13 @@
return computeOrientedBoundingBox(points, meanVector);
}
- static OrientedBoundingBox computeOrientedBoundingBox(float[] originalPoints) {
+ /**
+ * Computes an oriented, minimum bounding box of a set of points.
+ *
+ * @param originalPoints
+ * @return an oriented bounding box
+ */
+ public static OrientedBoundingBox computeOrientedBoundingBox(float[] originalPoints) {
int size = originalPoints.length;
float[] points = new float[size];
for (int i = 0; i < size; i++) {
diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl
index c0c2d03..2124e85 100644
--- a/core/java/android/os/IMountService.aidl
+++ b/core/java/android/os/IMountService.aidl
@@ -99,6 +99,11 @@
void unmountSecureContainer(String id);
/*
+ * Rename an unmounted secure container.
+ */
+ void renameSecureContainer(String oldId, String newId);
+
+ /*
* Returns the filesystem path of a mounted secure container.
*/
String getSecureContainerPath(String id);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index dc40113..8172ac4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -450,6 +450,21 @@
public static final String ACTION_SEARCH_SETTINGS =
"android.search.action.SEARCH_SETTINGS";
+ /**
+ * Activity Action: Show general device information settings (serial
+ * number, software version, phone number, etc.).
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_DEVICE_INFO_SETTINGS =
+ "android.settings.DEVICE_INFO_SETTINGS";
+
// End of Intent actions for Settings
private static final String JID_RESOURCE_PREFIX = "android";
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 2da2258..5b48bd2 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -17,7 +17,6 @@
package android.speech;
import android.os.Bundle;
-import android.speech.RecognitionResult;
/**
* Listener for speech recognition events, used with RecognitionService.
@@ -26,35 +25,55 @@
* {@hide}
*/
interface IRecognitionListener {
- /** Called when the endpointer is ready for the user to start speaking. */
- void onReadyForSpeech(in Bundle noiseParams);
+ /**
+ * Called when the endpointer is ready for the user to start speaking.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ void onReadyForSpeech(in Bundle params);
- /** The user has started to speak. */
+ /**
+ * The user has started to speak.
+ */
void onBeginningOfSpeech();
- /** The sound level in the audio stream has changed. */
+ /**
+ * The sound level in the audio stream has changed.
+ *
+ * @param rmsdB the new RMS dB value
+ */
void onRmsChanged(in float rmsdB);
/**
- * More sound has been received. Buffer is a byte buffer containing
- * a sequence of 16-bit shorts.
+ * More sound has been received.
+ *
+ * @param buffer the byte buffer containing a sequence of 16-bit shorts.
*/
void onBufferReceived(in byte[] buffer);
- /** Called after the user stops speaking. */
+ /**
+ * Called after the user stops speaking.
+ */
void onEndOfSpeech();
/**
- * A network or recognition error occurred. The code is defined in
- * {@link android.speech.RecognitionResult}
+ * A network or recognition error occurred.
+ *
+ * @param error code is defined in {@link RecognitionManager}
*/
void onError(in int error);
- /**
+ /**
* Called when recognition results are ready.
- * @param results: an ordered list of the most likely results (N-best list).
- * @param key: a key associated with the results. The same results can
- * be retrieved asynchronously later using the key, if available.
+ *
+ * @param results a Bundle containing the most likely results (N-best list).
*/
- void onResults(in List<RecognitionResult> results, long key);
+ void onResults(in Bundle results);
+
+ /**
+ * Called when recognition partial results are ready.
+ *
+ * @param results a Bundle containing the current most likely result.
+ */
+ void onPartialResults(in Bundle results);
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index a18c380..ca9af15 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -16,22 +16,41 @@
package android.speech;
+import android.os.Bundle;
import android.content.Intent;
import android.speech.IRecognitionListener;
-import android.speech.RecognitionResult;
-// A Service interface to speech recognition. Call startListening when
-// you want to begin capturing audio; RecognitionService will automatically
-// determine when the user has finished speaking, stream the audio to the
-// recognition servers, and notify you when results are ready.
-/** {@hide} */
+/**
+* A Service interface to speech recognition. Call startListening when
+* you want to begin capturing audio; RecognitionService will automatically
+* determine when the user has finished speaking, stream the audio to the
+* recognition servers, and notify you when results are ready. In most of the cases,
+* this class should not be used directly, instead use {@link RecognitionManager} for
+* accessing recognition service.
+* {@hide}
+*/
interface IRecognitionService {
- // Start listening for speech. Can only call this from one thread at once.
- // see RecognizerIntent.java for constants used to specify the intent.
- void startListening(in Intent recognizerIntent,
- in IRecognitionListener listener);
-
- List<RecognitionResult> getRecognitionResults(in long key);
+ /**
+ * Starts listening for speech. Please note that the recognition service supports
+ * one listener only, therefore, if this function is called from two different threads,
+ * only the latest one will get the notifications
+ *
+ * @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
+ */
+ void startListening(in Intent recognizerIntent, in IRecognitionListener listener);
+ /**
+ * 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.
+ */
+ void stopListening();
+
+ /**
+ * Cancels the speech recognition.
+ */
void cancel();
}
diff --git a/core/java/android/speech/RecognitionListener.java b/core/java/android/speech/RecognitionListener.java
new file mode 100644
index 0000000..eab3f40
--- /dev/null
+++ b/core/java/android/speech/RecognitionListener.java
@@ -0,0 +1,96 @@
+/*
+ * 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.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Used for receiving notifications from the RecognitionManager when the
+ * recognition related events occur. All the callbacks are executed on the
+ * 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.
+ *
+ * @param params parameters set by the recognition service. Reserved for future use.
+ */
+ void onReadyForSpeech(Bundle params);
+
+ /**
+ * The user has started to speak.
+ */
+ void onBeginningOfSpeech();
+
+ /**
+ * 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
+ */
+ void onRmsChanged(float rmsdB);
+
+ /**
+ * More sound has been received. The purpose of this function is to allow giving feedback to the
+ * user regarding the captured audio. There is no guarantee that this method will be called.
+ *
+ * @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.
+ */
+ void onBufferReceived(byte[] buffer);
+
+ /**
+ * Called after the user stops speaking.
+ */
+ void onEndOfSpeech();
+
+ /**
+ * A network or recognition error occurred.
+ *
+ * @param error code is defined in {@link RecognitionManager}
+ */
+ void onError(int error);
+
+ /**
+ * Called 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#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+ */
+ void onResults(Bundle results);
+
+ /**
+ * Called when partial recognition results are available. The callback might be called at any
+ * time between {@link #onBeginningOfSpeech()} and {@link #onResults(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#RECOGNITION_RESULTS_STRING_ARRAY} as a parameter
+ */
+ void onPartialResults(Bundle partialResults);
+
+}
diff --git a/core/java/android/speech/RecognitionManager.java b/core/java/android/speech/RecognitionManager.java
new file mode 100644
index 0000000..79ae480
--- /dev/null
+++ b/core/java/android/speech/RecognitionManager.java
@@ -0,0 +1,321 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * 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.
+ */
+public class RecognitionManager {
+ /** DEBUG value to enable verbose debug prints */
+ private final static boolean DBG = false;
+
+ /** Log messages identifier */
+ private static final String TAG = "RecognitionManager";
+
+ /**
+ * Used to retrieve an {@code ArrayList<String>} from the {@link Bundle} passed to the
+ * {@link RecognitionListener#onResults(Bundle)} and
+ * {@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";
+
+ /** The actual RecognitionService endpoint */
+ private IRecognitionService mService;
+
+ /** The connection to the actual service */
+ private Connection mConnection;
+
+ /** 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;
+
+ /**
+ * RecognitionManager was not initialized yet, most probably because
+ * {@link RecognitionListener#onInit()} was not called yet.
+ */
+ public static final int MANAGER_NOT_INITIALIZED_ERROR = 9;
+
+ /**
+ * The right way to create a RecognitionManager is by using
+ * {@link #createRecognitionManager} static factory method
+ */
+ private RecognitionManager(final RecognitionListener listener, final Context context) {
+ mInternalRecognitionListener = new InternalRecognitionListener();
+ mContext = context;
+ mListener = listener;
+ }
+
+ /**
+ * Basic ServiceConnection which just records mService variable.
+ */
+ private class Connection implements ServiceConnection {
+
+ public synchronized void onServiceConnected(final ComponentName name,
+ final IBinder service) {
+ mService = IRecognitionService.Stub.asInterface(service);
+ if (mListener != null) {
+ mListener.onInit();
+ }
+ if (DBG) Log.d(TAG, "onServiceConnected - Success");
+ }
+
+ public void onServiceDisconnected(final ComponentName name) {
+ mService = null;
+ mConnection = null;
+ 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.
+ *
+ * @param context with which RecognitionManager will be created
+ * @return {@code true} if recognition is available, {@code false} otherwise
+ */
+ public static boolean isRecognitionAvailable(final Context context) {
+ final List<ResolveInfo> list = context.getPackageManager().queryIntentServices(
+ new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
+ return list != null && list.size() != 0;
+ }
+
+ /**
+ * Factory method to create a new 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.
+ */
+ 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)");
+ }
+ 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;
+ }
+
+ /**
+ * 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
+ */
+ 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;
+ }
+
+ /**
+ * Starts listening for speech.
+ *
+ * @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.
+ */
+ public void startListening(Intent recognizerIntent) {
+ if (recognizerIntent == null) {
+ throw new IllegalArgumentException("recognizerIntent argument cannot be null");
+ }
+ if (!connectToService("startListening")) {
+ return; // service is not connected yet, reconnect in progress
+ }
+ 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);
+ }
+ }
+
+ /**
+ * Stops listening for speech. Speech captured so far will be recognized as if the user had
+ * stopped speaking at this point. Note that in the default case, this does not need to be
+ * 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.
+ */
+ public void stopListening() {
+ if (mService == null) {
+ return; // service is not connected, but no need to reconnect at this point
+ }
+ try {
+ mService.stopListening();
+ if (DBG) Log.d(TAG, "service stop listening command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "stopListening() failed", e);
+ mInternalRecognitionListener.onError(CLIENT_ERROR);
+ }
+ }
+
+ /**
+ * Cancels the speech recognition.
+ */
+ public void cancel() {
+ if (mService == null) {
+ return; // service is not connected, but no need to reconnect at this point
+ }
+ try {
+ mService.cancel();
+ if (DBG) Log.d(TAG, "service cancel command succeded");
+ } catch (final RemoteException e) {
+ Log.e(TAG, "cancel() failed", e);
+ mInternalRecognitionListener.onError(CLIENT_ERROR);
+ }
+ }
+
+ /**
+ * Destroys the 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);
+ }
+ mService = null;
+ }
+
+ /**
+ * Internal wrapper of IRecognitionListener which will propagate the results
+ * to RecognitionListener
+ */
+ private class InternalRecognitionListener extends IRecognitionListener.Stub {
+
+ public void onBeginningOfSpeech() {
+ if (mListener != null) {
+ mListener.onBeginningOfSpeech();
+ }
+ }
+
+ public void onBufferReceived(final byte[] buffer) {
+ if (mListener != null) {
+ mListener.onBufferReceived(buffer);
+ }
+ }
+
+ public void onEndOfSpeech() {
+ if (mListener != null) {
+ mListener.onEndOfSpeech();
+ }
+ }
+
+ public void onError(final int error) {
+ if (mListener != null) {
+ mListener.onError(error);
+ }
+ }
+
+ public void onReadyForSpeech(final Bundle noiseParams) {
+ if (mListener != null) {
+ mListener.onReadyForSpeech(noiseParams);
+ }
+ }
+
+ public void onResults(final Bundle results) {
+ if (mListener != null) {
+ mListener.onResults(results);
+ }
+ }
+
+ public void onPartialResults(final Bundle results) {
+ if (mListener != null) {
+ mListener.onPartialResults(results);
+ }
+ }
+
+ public void onRmsChanged(final float rmsdB) {
+ if (mListener != null) {
+ mListener.onRmsChanged(rmsdB);
+ }
+ }
+ }
+}
diff --git a/core/java/android/speech/RecognitionResult.aidl b/core/java/android/speech/RecognitionResult.aidl
deleted file mode 100644
index 59e53ab..0000000
--- a/core/java/android/speech/RecognitionResult.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2009 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;
-
-parcelable RecognitionResult;
diff --git a/core/java/android/speech/RecognitionResult.java b/core/java/android/speech/RecognitionResult.java
deleted file mode 100644
index 95715ee..0000000
--- a/core/java/android/speech/RecognitionResult.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2009 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.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * RecognitionResult is a passive object that stores a single recognized query
- * and its search result.
- *
- * TODO: Revisit and improve this class, reconciling the different types of actions and
- * the different ways they are represented. Maybe we should have a separate result object
- * for each type, and put them (type/value) in bundle?
- * {@hide}
- */
-public class RecognitionResult implements Parcelable {
- /**
- * Status of the recognize request.
- */
- public static final int NETWORK_TIMEOUT = 1; // Network operation timed out.
-
- public static final int NETWORK_ERROR = 2; // Other network related errors.
-
- public static final int AUDIO_ERROR = 3; // Audio recording error.
-
- public static final int SERVER_ERROR = 4; // Server sends error status.
-
- public static final int CLIENT_ERROR = 5; // Other client side errors.
-
- public static final int SPEECH_TIMEOUT = 6; // No speech input
-
- public static final int NO_MATCH = 7; // No recognition result matched.
-
- public static final int SERVICE_BUSY = 8; // RecognitionService busy.
-
- /**
- * Type of the recognition results.
- */
- public static final int RAW_RECOGNITION_RESULT = 0;
-
- public static final int WEB_SEARCH_RESULT = 1;
-
- public static final int CONTACT_RESULT = 2;
-
- public static final int ACTION_RESULT = 3;
-
- /**
- * A factory method to create a raw RecognitionResult
- *
- * @param sentence the recognized text.
- */
- public static RecognitionResult newRawRecognitionResult(String sentence) {
- return new RecognitionResult(RAW_RECOGNITION_RESULT, sentence, null, null);
- }
-
- /**
- * A factory method to create a RecognitionResult for contacts.
- *
- * @param contact the contact name.
- * @param phoneType the phone type.
- * @param callAction whether this result included a command to "call", or
- * just the contact name.
- */
- public static RecognitionResult newContactResult(String contact, int phoneType,
- boolean callAction) {
- return new RecognitionResult(CONTACT_RESULT, contact, phoneType, callAction);
- }
-
- /**
- * A factory method to create a RecognitionResult for a web search query.
- *
- * @param query the query string.
- * @param html the html page of the search result.
- * @param url the url that performs the search with the query.
- */
- public static RecognitionResult newWebResult(String query, String html, String url) {
- return new RecognitionResult(WEB_SEARCH_RESULT, query, html, url);
- }
-
- /**
- * A factory method to create a RecognitionResult for an action.
- *
- * @param action the action type
- * @param query the query string associated with that action.
- */
- public static RecognitionResult newActionResult(int action, String query) {
- return new RecognitionResult(ACTION_RESULT, action, query);
- }
-
- public static final Parcelable.Creator<RecognitionResult> CREATOR =
- new Parcelable.Creator<RecognitionResult>() {
-
- public RecognitionResult createFromParcel(Parcel in) {
- return new RecognitionResult(in);
- }
-
- public RecognitionResult[] newArray(int size) {
- return new RecognitionResult[size];
- }
- };
-
- /**
- * Result type.
- */
- public final int mResultType;
-
- /**
- * The recognized string when mResultType is WEB_SEARCH_RESULT. The name of
- * the contact when mResultType is CONTACT_RESULT. The relevant query when
- * mResultType is ACTION_RESULT.
- */
- public final String mText;
-
- /**
- * The HTML result page for the query. If this is null, then the application
- * must use the url field to get the HTML result page.
- */
- public final String mHtml;
-
- /**
- * The url to get the result page for the query string. The application must
- * use this url instead of performing the search with the query.
- */
- public final String mUrl;
-
- /**
- * Phone number type. This is valid only when mResultType == CONTACT_RESULT.
- */
- public final int mPhoneType;
-
- /**
- * Action type. This is valid only when mResultType == ACTION_RESULT.
- */
- public final int mAction;
-
- /**
- * Whether a contact recognition result included a command to "call". This
- * is valid only when mResultType == CONTACT_RESULT.
- */
- public final boolean mCallAction;
-
- private RecognitionResult(int type, int action, String query) {
- mResultType = type;
- mAction = action;
- mText = query;
- mHtml = null;
- mUrl = null;
- mPhoneType = -1;
- mCallAction = false;
- }
-
- private RecognitionResult(int type, String query, String html, String url) {
- mResultType = type;
- mText = query;
- mHtml = html;
- mUrl = url;
- mPhoneType = -1;
- mAction = -1;
- mCallAction = false;
- }
-
- private RecognitionResult(int type, String query, int phoneType, boolean callAction) {
- mResultType = type;
- mText = query;
- mPhoneType = phoneType;
- mHtml = null;
- mUrl = null;
- mAction = -1;
- mCallAction = callAction;
- }
-
- private RecognitionResult(Parcel in) {
- mResultType = in.readInt();
- mText = in.readString();
- mHtml = in.readString();
- mUrl = in.readString();
- mPhoneType = in.readInt();
- mAction = in.readInt();
- mCallAction = (in.readInt() == 1);
- }
-
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(mResultType);
- out.writeString(mText);
- out.writeString(mHtml);
- out.writeString(mUrl);
- out.writeInt(mPhoneType);
- out.writeInt(mAction);
- out.writeInt(mCallAction ? 1 : 0);
- }
-
- @Override
- public String toString() {
- String resultType[] = {
- "RAW", "WEB", "CONTACT", "ACTION"
- };
- return "[type=" + resultType[mResultType] + ", text=" + mText + ", mUrl=" + mUrl
- + ", html=" + mHtml + ", mAction=" + mAction + ", mCallAction=" + mCallAction + "]";
- }
-
- public int describeContents() {
- // no special description
- return 0;
- }
-}
diff --git a/core/java/android/speech/RecognitionServiceUtil.java b/core/java/android/speech/RecognitionServiceUtil.java
deleted file mode 100644
index 4207543..0000000
--- a/core/java/android/speech/RecognitionServiceUtil.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2009 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.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.speech.RecognitionResult;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Utils for Google's network-based speech recognizer, which lets you perform
- * speech-to-text translation through RecognitionService. IRecognitionService
- * and IRecognitionListener are the core interfaces; you begin recognition
- * through IRecognitionService and subscribe to callbacks about when the user
- * stopped speaking, results come in, errors, etc. through IRecognitionListener.
- * RecognitionServiceUtil includes default IRecognitionListener and
- * ServiceConnection implementations to reduce the amount of boilerplate.
- *
- * The Service provides no user interface. See RecognitionActivity if you
- * want the standard voice search UI.
- *
- * Below is a small skeleton of how to use the recognizer:
- *
- * ServiceConnection conn = new RecognitionServiceUtil.Connection();
- * mContext.bindService(RecognitionServiceUtil.sDefaultIntent,
- * conn, Context.BIND_AUTO_CREATE);
- * IRecognitionListener listener = new RecognitionServiceWrapper.NullListener() {
- * public void onResults(List<String> results) {
- * // Do something with recognition transcripts
- * }
- * }
- *
- * // Must wait for conn.mService to be populated, then call below
- * conn.mService.startListening(null, listener);
- *
- * {@hide}
- */
-public class RecognitionServiceUtil {
- public static final Intent sDefaultIntent = new Intent(
- RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-
- // Recognize request parameters
- public static final String USE_LOCATION = "useLocation";
- public static final String CONTACT_AUTH_TOKEN = "contactAuthToken";
-
- // Bundles
- public static final String NOISE_LEVEL = "NoiseLevel";
- public static final String SIGNAL_NOISE_RATIO = "SignalNoiseRatio";
-
- private RecognitionServiceUtil() {}
-
- /**
- * IRecognitionListener which does nothing in response to recognition
- * callbacks. You can subclass from this and override only the methods
- * whose events you want to respond to.
- */
- public static class NullListener extends IRecognitionListener.Stub {
- public void onReadyForSpeech(Bundle bundle) {}
- public void onBeginningOfSpeech() {}
- public void onRmsChanged(float rmsdB) {}
- public void onBufferReceived(byte[] buf) {}
- public void onEndOfSpeech() {}
- public void onError(int error) {}
- public void onResults(List<RecognitionResult> results, long key) {}
- }
-
- /**
- * Basic ServiceConnection which just records mService variable.
- */
- public static class Connection implements ServiceConnection {
- public IRecognitionService mService;
-
- public synchronized void onServiceConnected(ComponentName name, IBinder service) {
- mService = IRecognitionService.Stub.asInterface(service);
- }
-
- public void onServiceDisconnected(ComponentName name) {
- mService = null;
- }
- }
-}
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 6c5f912..49991bd 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -84,6 +84,42 @@
public static final String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
/**
+ * The minimum length of an utterance. We will not stop recording before this amount of time.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If you don't
+ * have a very good reason to change these, you should leave them as they are. Note also that
+ * certain values may cause undesired or unexpected results - use judiciously! Additionally,
+ * depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS";
+
+ /**
+ * The amount of time that it should take after we stop hearing speech to consider the input
+ * complete.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If
+ * you don't have a very good reason to change these, you should leave them as they are. Note
+ * also that certain values may cause undesired or unexpected results - use judiciously!
+ * Additionally, depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS";
+
+ /**
+ * The amount of time that it should take after we stop hearing speech to consider the input
+ * possibly complete. This is used to prevent the endpointer cutting off during very short
+ * mid-speech pauses.
+ *
+ * Note that it is extremely rare you'd want to specify this value in an intent. If
+ * you don't have a very good reason to change these, you should leave them as they are. Note
+ * also that certain values may cause undesired or unexpected results - use judiciously!
+ * Additionally, depending on the recognizer implementation, these values may have no effect.
+ */
+ public static final String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS =
+ "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS";
+
+ /**
* Informs the recognizer which speech model to prefer when performing
* {@link #ACTION_RECOGNIZE_SPEECH}. The recognizer uses this
* information to fine tune the results. This extra is required. Activities implementing
@@ -111,8 +147,9 @@
public static final String EXTRA_PROMPT = "android.speech.extra.PROMPT";
/**
- * Optional language override to inform the recognizer that it should expect speech in
- * a language different than the one set in the {@link java.util.Locale#getDefault()}.
+ * Optional IETF language tag (as defined by BCP 47), for example "en-US". This tag informs the
+ * recognizer to perform speech recognition in a language different than the one set in the
+ * {@link java.util.Locale#getDefault()}.
*/
public static final String EXTRA_LANGUAGE = "android.speech.extra.LANGUAGE";
@@ -121,7 +158,7 @@
* will choose how many results to return. Must be an integer.
*/
public static final String EXTRA_MAX_RESULTS = "android.speech.extra.MAX_RESULTS";
-
+
/**
* When the intent is {@link #ACTION_RECOGNIZE_SPEECH}, the speech input activity will
* return results to you via the activity results mechanism. Alternatively, if you use this
@@ -157,8 +194,6 @@
/**
* Triggers the voice search settings activity.
- *
- * @hide pending API council approval, to be unhidden for Froyo
*/
public static final String ACTION_VOICE_SEARCH_SETTINGS =
"android.speech.action.VOICE_SEARCH_SETTINGS";
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 50bfefc..f991df7 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -147,12 +147,9 @@
public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
ViewConfiguration config = ViewConfiguration.get(context);
- DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mContext = context;
mListener = listener;
mEdgeSlop = config.getScaledEdgeSlop();
- mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
- mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
}
public boolean onTouchEvent(MotionEvent event) {
@@ -165,6 +162,11 @@
event.getPointerCount() >= 2) {
// We have a new multi-finger gesture
+ // as orientation can change, query the metrics in touch down
+ DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+ mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
+ mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
+
// Be paranoid in case we missed an event
reset();
@@ -185,12 +187,16 @@
final float x1 = getRawX(event, 1);
final float y1 = getRawY(event, 1);
- boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop ||
- x1 < edgeSlop || y1 < edgeSlop;
- boolean p1sloppy = x0 > rightSlop || y0 > bottomSlop ||
- x1 > rightSlop || y1 > bottomSlop;
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+ || x0 > rightSlop || y0 > bottomSlop;
+ boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+ || x1 > rightSlop || y1 > bottomSlop;
- if (p0sloppy) {
+ if(p0sloppy && p1sloppy) {
+ mFocusX = -1;
+ mFocusY = -1;
+ mSloppyGesture = true;
+ } else if (p0sloppy) {
mFocusX = event.getX(1);
mFocusY = event.getY(1);
mSloppyGesture = true;
@@ -211,12 +217,15 @@
final float x1 = getRawX(event, 1);
final float y1 = getRawY(event, 1);
- boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop ||
- x1 < edgeSlop || y1 < edgeSlop;
- boolean p1sloppy = x0 > rightSlop || y0 > bottomSlop ||
- x1 > rightSlop || y1 > bottomSlop;
+ boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
+ || x0 > rightSlop || y0 > bottomSlop;
+ boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
+ || x1 > rightSlop || y1 > bottomSlop;
- if (p0sloppy) {
+ if(p0sloppy && p1sloppy) {
+ mFocusX = -1;
+ mFocusY = -1;
+ } else if (p0sloppy) {
mFocusX = event.getX(1);
mFocusY = event.getY(1);
} else if (p1sloppy) {
@@ -226,6 +235,14 @@
mSloppyGesture = false;
mGestureInProgress = mListener.onScaleBegin(this);
}
+ } else if ((action == MotionEvent.ACTION_POINTER_1_UP
+ || action == MotionEvent.ACTION_POINTER_2_UP)
+ && mSloppyGesture) {
+ // Set focus point to the remaining finger
+ int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+ >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+ mFocusX = event.getX(id);
+ mFocusY = event.getY(id);
}
} else {
// Transform gesture in progress - attempt to handle it
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 90ed65d..d99c2c0 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -51,6 +51,11 @@
*/
private Bundle mSslPrefTable;
+ /**
+ * Flag indicating that a client reponse is pending.
+ */
+ private boolean mResponsePending;
+
// Message id for handling the response
private static final int HANDLE_RESPONSE = 100;
@@ -191,6 +196,7 @@
// if we do not have information on record, ask
// the user (display a dialog)
CallbackProxy proxy = loader.getFrame().getCallbackProxy();
+ mResponsePending = true;
proxy.onReceivedSslError(this, error);
}
@@ -202,7 +208,11 @@
* Proceed with the SSL certificate.
*/
public void proceed() {
- sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0, mLoaderQueue.poll()));
+ if (mResponsePending) {
+ mResponsePending = false;
+ sendMessage(obtainMessage(HANDLE_RESPONSE, 1, 0,
+ mLoaderQueue.poll()));
+ }
}
/**
@@ -210,7 +220,11 @@
* the error.
*/
public void cancel() {
- sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0, mLoaderQueue.poll()));
+ if (mResponsePending) {
+ mResponsePending = false;
+ sendMessage(obtainMessage(HANDLE_RESPONSE, 0, 0,
+ mLoaderQueue.poll()));
+ }
}
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 56650a6..93e72ff 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -4607,14 +4607,11 @@
break;
}
} else {
- if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
- // if mPreventDrag is not confirmed, treat it as
- // no so that it won't block tap or double tap.
- mPreventDrag = PREVENT_DRAG_NO;
- mPreventLongPress = false;
- mPreventDoubleTap = false;
- }
- if (mPreventDrag == PREVENT_DRAG_NO) {
+ // mPreventDrag can be PREVENT_DRAG_MAYBE_YES in
+ // TOUCH_INIT_MODE. To give WebCoreThread a little
+ // more time to send PREVENT_TOUCH_ID, we check
+ // again in responding RELEASE_SINGLE_TAP.
+ if (mPreventDrag != PREVENT_DRAG_YES) {
if (mTouchMode == TOUCH_INIT_MODE) {
mPrivateHandler.sendMessageDelayed(
mPrivateHandler.obtainMessage(
@@ -5631,6 +5628,13 @@
break;
}
case RELEASE_SINGLE_TAP: {
+ if (mPreventDrag == PREVENT_DRAG_MAYBE_YES) {
+ // if mPreventDrag is not confirmed, treat it as
+ // no so that it won't block tap.
+ mPreventDrag = PREVENT_DRAG_NO;
+ mPreventLongPress = false;
+ mPreventDoubleTap = false;
+ }
if (mPreventDrag == PREVENT_DRAG_NO) {
mTouchMode = TOUCH_DONE_MODE;
doShortPress();
@@ -5863,7 +5867,7 @@
// updates is a C++ pointer to a Vector of
// AnimationValues that we apply to the layers.
// The Vector is deallocated in nativeUpdateLayers().
- nativeUpdateLayers(mRootLayer, updates);
+ nativeUpdateLayers(updates);
}
invalidate();
break;
@@ -5985,10 +5989,22 @@
}
mFullScreenHolder = new PluginFullScreenHolder(
WebView.this, data.mNpp);
+ // as we are sharing the View between full screen and
+ // embedded mode, we have to remove the
+ // AbsoluteLayout.LayoutParams set by embedded mode to
+ // ViewGroup.LayoutParams before adding it to the dialog
+ data.mView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
mFullScreenHolder.setContentView(data.mView);
mFullScreenHolder.setCancelable(false);
mFullScreenHolder.setCanceledOnTouchOutside(false);
mFullScreenHolder.show();
+ } else if (mFullScreenHolder == null) {
+ // this may happen if user dismisses the fullscreen and
+ // then the WebCore re-position message finally reached
+ // the UI thread.
+ break;
}
// move the matching embedded view fully into the view so
// that touch will be valid instead of rejected due to out
@@ -6602,7 +6618,7 @@
private native void nativeDestroyLayer(int layer);
private native int nativeEvaluateLayersAnimations(int layer);
private native boolean nativeLayersHaveAnimations(int layer);
- private native void nativeUpdateLayers(int layer, int updates);
+ private native void nativeUpdateLayers(int updates);
private native void nativeDrawLayers(int layer,
int scrollX, int scrollY,
int width, int height,
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 3d8d296..3995026 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -64,10 +64,10 @@
}
static jboolean
-android_media_AudioSystem_isMusicActive(JNIEnv *env, jobject thiz)
+android_media_AudioSystem_isStreamActive(JNIEnv *env, jobject thiz, jint stream)
{
bool state = false;
- AudioSystem::isMusicActive(&state);
+ AudioSystem::isStreamActive(stream, &state);
return state;
}
@@ -195,7 +195,7 @@
{"getParameters", "(Ljava/lang/String;)Ljava/lang/String;", (void *)android_media_AudioSystem_getParameters},
{"muteMicrophone", "(Z)I", (void *)android_media_AudioSystem_muteMicrophone},
{"isMicrophoneMuted", "()Z", (void *)android_media_AudioSystem_isMicrophoneMuted},
- {"isMusicActive", "()Z", (void *)android_media_AudioSystem_isMusicActive},
+ {"isStreamActive", "(I)Z", (void *)android_media_AudioSystem_isStreamActive},
{"setDeviceConnectionState", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
{"getDeviceConnectionState", "(ILjava/lang/String;)I", (void *)android_media_AudioSystem_getDeviceConnectionState},
{"setPhoneState", "(I)I", (void *)android_media_AudioSystem_setPhoneState},
diff --git a/docs/html/guide/developing/debug-tasks.jd b/docs/html/guide/developing/debug-tasks.jd
index 975f6998..a980efc 100644
--- a/docs/html/guide/developing/debug-tasks.jd
+++ b/docs/html/guide/developing/debug-tasks.jd
@@ -80,13 +80,25 @@
<h2 id="additionaldebugging">Debugging and Testing with Dev Tools</h2>
-<p>With the Dev Tools application, you can turn on a number of settings that will
-make it easier to test and debug your applications. The Dev Tools application is automatically
-installed on all system images included with the SDK. The source code for the Dev Tools application
-is also provided in the SDK samples so that you may build it and then install the application on any
-development device.</p>
+<p>With the Dev Tools application, you can enable a number of settings on your device that will
+make it easier to test and debug your applications.</p>
-<p>To get to the development settings page on the emulator, launch the Dev Tools application and
+<p>The Dev Tools application is installed by default
+on all system images included with the SDK, so you can use it with the Android Emulator. If you'd
+like to install the Dev Tools application on a real development device, you can copy the
+application from your emulator and then install it on your device using ADB. To copy the
+application from a running emulator, execute:
+</p>
+<pre>
+adb -e pull /system/app/Development.apk ./Development.apk
+</pre>
+<p>This copies the .apk file into the current directory. Then install it on your connected device
+with:</p>
+<pre>
+adb -d install Development.apk
+</pre>
+
+<p>To get started, launch the Dev Tools application and
select Development Settings. This will open the Development Settings page with the
following options (among others):</p>
@@ -132,7 +144,7 @@
can happen during debugging.</dd>
</dl>
-<p>These settings will be remembered across emulator restarts. </p>
+<p>These settings will be remembered across emulator restarts.</p>
<h2 id="DebuggingWebPages">Debugging Web Pages</h2>
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index f935bb9..b42bf54 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -194,8 +194,8 @@
// set audio mode in audio hardware (see AudioSystem::audio_mode)
static status_t setMode(int mode);
- // returns true if tracks are active on AudioSystem::MUSIC stream
- static status_t isMusicActive(bool *state);
+ // returns true in *state if tracks are active on the specified stream
+ static status_t isStreamActive(int stream, bool *state);
// set/get audio hardware parameters. The function accepts a list of parameters
// key value pairs in the form: key1=value1;key2=value2;...
diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h
index a46c727..b689dcbc 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -97,8 +97,8 @@
virtual status_t setMicMute(bool state) = 0;
virtual bool getMicMute() const = 0;
- // is a music stream active?
- virtual bool isMusicActive() const = 0;
+ // is any track active on this stream?
+ virtual bool isStreamActive(int stream) const = 0;
virtual status_t setParameters(int ioHandle, const String8& keyValuePairs) = 0;
virtual String8 getParameters(int ioHandle, const String8& keys) = 0;
diff --git a/include/media/stagefright/AMRWriter.h b/include/media/stagefright/AMRWriter.h
new file mode 100644
index 0000000..372909a
--- /dev/null
+++ b/include/media/stagefright/AMRWriter.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AMR_WRITER_H_
+
+#define AMR_WRITER_H_
+
+#include <stdio.h>
+
+#include <media/stagefright/MediaWriter.h>
+#include <utils/threads.h>
+
+namespace android {
+
+struct MediaSource;
+
+struct AMRWriter : public MediaWriter {
+ AMRWriter(const char *filename);
+ AMRWriter(int fd);
+
+ status_t initCheck() const;
+
+ virtual status_t addSource(const sp<MediaSource> &source);
+ virtual bool reachedEOS();
+ virtual status_t start();
+ virtual void stop();
+
+protected:
+ virtual ~AMRWriter();
+
+private:
+ Mutex mLock;
+
+ FILE *mFile;
+ status_t mInitCheck;
+ sp<MediaSource> mSource;
+ bool mStarted;
+ volatile bool mDone;
+ bool mReachedEOS;
+ pthread_t mThread;
+
+ static void *ThreadWrapper(void *);
+ void threadFunc();
+
+ AMRWriter(const AMRWriter &);
+ AMRWriter &operator=(const AMRWriter &);
+};
+
+} // namespace android
+
+#endif // AMR_WRITER_H_
diff --git a/include/media/stagefright/AudioSource.h b/include/media/stagefright/AudioSource.h
index e129958..eb00140 100644
--- a/include/media/stagefright/AudioSource.h
+++ b/include/media/stagefright/AudioSource.h
@@ -18,16 +18,20 @@
#define AUDIO_SOURCE_H_
+#include <media/AudioSystem.h>
#include <media/stagefright/MediaSource.h>
namespace android {
class AudioRecord;
+struct MediaBufferGroup;
-class AudioSource {
-public:
- AudioSource(int inputSource);
- virtual ~AudioSource();
+struct AudioSource : public MediaSource {
+ // Note that the "channels" parameter is _not_ the number of channels,
+ // but a bitmask of AudioSystem::audio_channels constants.
+ AudioSource(
+ int inputSource, uint32_t sampleRate,
+ uint32_t channels = AudioSystem::CHANNEL_IN_MONO);
status_t initCheck() const;
@@ -38,9 +42,16 @@
virtual status_t read(
MediaBuffer **buffer, const ReadOptions *options = NULL);
+protected:
+ virtual ~AudioSource();
+
private:
+ enum { kMaxBufferSize = 8192 };
+
AudioRecord *mRecord;
status_t mInitCheck;
+ bool mStarted;
+ MediaBufferGroup *mGroup;
AudioSource(const AudioSource &);
AudioSource &operator=(const AudioSource &);
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index 2ca04fa..6b93f19 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -20,8 +20,8 @@
#include <stdio.h>
+#include <media/stagefright/MediaWriter.h>
#include <utils/List.h>
-#include <utils/RefBase.h>
#include <utils/threads.h>
namespace android {
@@ -30,15 +30,15 @@
class MediaSource;
class MetaData;
-class MPEG4Writer : public RefBase {
+class MPEG4Writer : public MediaWriter {
public:
MPEG4Writer(const char *filename);
MPEG4Writer(int fd);
- void addSource(const sp<MediaSource> &source);
- status_t start();
- bool reachedEOS();
- void stop();
+ virtual status_t addSource(const sp<MediaSource> &source);
+ virtual status_t start();
+ virtual bool reachedEOS();
+ virtual void stop();
void beginBox(const char *fourcc);
void writeInt8(int8_t x);
diff --git a/include/media/stagefright/MediaWriter.h b/include/media/stagefright/MediaWriter.h
new file mode 100644
index 0000000..b8232c6
--- /dev/null
+++ b/include/media/stagefright/MediaWriter.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+#ifndef MEDIA_WRITER_H_
+
+#define MEDIA_WRITER_H_
+
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct MediaSource;
+
+struct MediaWriter : public RefBase {
+ MediaWriter() {}
+
+ virtual status_t addSource(const sp<MediaSource> &source) = 0;
+ virtual bool reachedEOS() = 0;
+ virtual status_t start() = 0;
+ virtual void stop() = 0;
+
+protected:
+ virtual ~MediaWriter() {}
+
+private:
+ MediaWriter(const MediaWriter &);
+ MediaWriter &operator=(const MediaWriter &);
+};
+
+} // namespace android
+
+#endif // MEDIA_WRITER_H_
diff --git a/libs/audioflinger/AudioFlinger.cpp b/libs/audioflinger/AudioFlinger.cpp
index ecfe1e0..5c58603 100644
--- a/libs/audioflinger/AudioFlinger.cpp
+++ b/libs/audioflinger/AudioFlinger.cpp
@@ -544,11 +544,11 @@
return mStreamTypes[stream].mute;
}
-bool AudioFlinger::isMusicActive() const
+bool AudioFlinger::isStreamActive(int stream) const
{
Mutex::Autolock _l(mLock);
for (uint32_t i = 0; i < mPlaybackThreads.size(); i++) {
- if (mPlaybackThreads.valueAt(i)->isMusicActive()) {
+ if (mPlaybackThreads.valueAt(i)->isStreamActive(stream)) {
return true;
}
}
@@ -1071,7 +1071,7 @@
return mStreamTypes[stream].mute;
}
-bool AudioFlinger::PlaybackThread::isMusicActive() const
+bool AudioFlinger::PlaybackThread::isStreamActive(int stream) const
{
Mutex::Autolock _l(mLock);
size_t count = mActiveTracks.size();
@@ -1079,7 +1079,7 @@
sp<Track> t = mActiveTracks[i].promote();
if (t == 0) continue;
Track* const track = t.get();
- if (t->type() == AudioSystem::MUSIC)
+ if (t->type() == stream)
return true;
}
return false;
diff --git a/libs/audioflinger/AudioFlinger.h b/libs/audioflinger/AudioFlinger.h
index 12c90eb..52999b1 100644
--- a/libs/audioflinger/AudioFlinger.h
+++ b/libs/audioflinger/AudioFlinger.h
@@ -100,7 +100,7 @@
virtual status_t setMicMute(bool state);
virtual bool getMicMute() const;
- virtual bool isMusicActive() const;
+ virtual bool isStreamActive(int stream) const;
virtual status_t setParameters(int ioHandle, const String8& keyValuePairs);
virtual String8 getParameters(int ioHandle, const String8& keys);
@@ -506,7 +506,7 @@
virtual float streamVolume(int stream) const;
virtual bool streamMute(int stream) const;
- bool isMusicActive() const;
+ bool isStreamActive(int stream) const;
sp<Track> createTrack_l(
const sp<AudioFlinger::Client>& client,
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index e39a357..c4d4f99 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -500,7 +500,7 @@
LOGV("Opening device: %s", deviceName);
AutoMutex _l(mLock);
-
+
fd = open(deviceName, O_RDWR);
if(fd < 0) {
LOGE("could not open %s, %s\n", deviceName, strerror(errno));
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index bb16215a..171881f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -797,7 +797,7 @@
public void setMode(int mode) {
IAudioService service = getService();
try {
- service.setMode(mode);
+ service.setMode(mode, mICallBack);
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setMode", e);
}
@@ -924,7 +924,7 @@
* @return true if any music tracks are active.
*/
public boolean isMusicActive() {
- return AudioSystem.isMusicActive();
+ return AudioSystem.isStreamActive(STREAM_MUSIC);
}
/*
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 482fc4f..bde8a47 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -54,7 +54,6 @@
import java.util.Map;
import java.util.Set;
-
/**
* The implementation of the volume manager service.
* <p>
@@ -231,6 +230,9 @@
// Forced device usage for communications
private int mForcedUseForComm;
+ // List of binder death handlers for setMode() client processes.
+ // The last process to have called setMode() is at the top of the list.
+ private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
///////////////////////////////////////////////////////////////////////////
// Construction
@@ -248,11 +250,13 @@
mVolumePanel = new VolumePanel(context, this);
mSettingsObserver = new SettingsObserver();
- mMode = AudioSystem.MODE_NORMAL;
mForcedUseForComm = AudioSystem.FORCE_NONE;
createAudioSystemThread();
readPersistedSettings();
createStreamStates();
+ // Call setMode() to initialize mSetModeDeathHandlers
+ mMode = AudioSystem.MODE_INVALID;
+ setMode(AudioSystem.MODE_NORMAL, null);
mMediaServerOk = true;
AudioSystem.setErrorCallback(mAudioSystemCallback);
loadSoundEffects();
@@ -582,8 +586,54 @@
return existingValue;
}
+ private class SetModeDeathHandler implements IBinder.DeathRecipient {
+ private IBinder mCb; // To be notified of client's death
+ private int mMode = AudioSystem.MODE_NORMAL; // Current mode set by this client
+
+ SetModeDeathHandler(IBinder cb) {
+ mCb = cb;
+ }
+
+ public void binderDied() {
+ synchronized(mSetModeDeathHandlers) {
+ Log.w(TAG, "setMode() client died");
+ int index = mSetModeDeathHandlers.indexOf(this);
+ if (index < 0) {
+ Log.w(TAG, "unregistered setMode() client died");
+ } else {
+ mSetModeDeathHandlers.remove(this);
+ // If dead client was a the top of client list,
+ // apply next mode in the stack
+ if (index == 0) {
+ // mSetModeDeathHandlers is never empty as the initial entry
+ // created when AudioService starts is never removed
+ SetModeDeathHandler hdlr = mSetModeDeathHandlers.get(0);
+ int mode = hdlr.getMode();
+ if (AudioService.this.mMode != mode) {
+ if (AudioSystem.setPhoneState(mode) == AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.this.mMode = mode;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void setMode(int mode) {
+ mMode = mode;
+ }
+
+ public int getMode() {
+ return mMode;
+ }
+
+ public IBinder getBinder() {
+ return mCb;
+ }
+ }
+
/** @see AudioManager#setMode(int) */
- public void setMode(int mode) {
+ public void setMode(int mode, IBinder cb) {
if (!checkAudioSettingsPermission("setMode()")) {
return;
}
@@ -599,6 +649,37 @@
if (mode != mMode) {
if (AudioSystem.setPhoneState(mode) == AudioSystem.AUDIO_STATUS_OK) {
mMode = mode;
+
+ synchronized(mSetModeDeathHandlers) {
+ SetModeDeathHandler hdlr = null;
+ Iterator iter = mSetModeDeathHandlers.iterator();
+ while (iter.hasNext()) {
+ SetModeDeathHandler h = (SetModeDeathHandler)iter.next();
+ if (h.getBinder() == cb) {
+ hdlr = h;
+ // Remove from client list so that it is re-inserted at top of list
+ iter.remove();
+ break;
+ }
+ }
+ if (hdlr == null) {
+ hdlr = new SetModeDeathHandler(cb);
+ // cb is null when setMode() is called by AudioService constructor
+ if (cb != null) {
+ // Register for client death notification
+ try {
+ cb.linkToDeath(hdlr, 0);
+ } catch (RemoteException e) {
+ // Client has died!
+ Log.w(TAG, "setMode() could not link to "+cb+" binder death");
+ }
+ }
+ }
+ // Last client to call setMode() is always at top of client list
+ // as required by SetModeDeathHandler.binderDied()
+ mSetModeDeathHandlers.add(0, hdlr);
+ hdlr.setMode(mode);
+ }
}
}
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
@@ -876,10 +957,10 @@
if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION) == AudioSystem.FORCE_BT_SCO) {
// Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
return AudioSystem.STREAM_BLUETOOTH_SCO;
- } else if (isOffhook) {
+ } else if (isOffhook || AudioSystem.isStreamActive(AudioSystem.STREAM_VOICE_CALL)) {
// Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL...");
return AudioSystem.STREAM_VOICE_CALL;
- } else if (AudioSystem.isMusicActive()) {
+ } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC)) {
// Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC...");
return AudioSystem.STREAM_MUSIC;
} else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
@@ -1285,7 +1366,7 @@
// Force creation of new IAudioflinger interface
if (!mMediaServerOk) {
Log.e(TAG, "Media server died.");
- AudioSystem.isMusicActive();
+ AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC);
sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SHARED_MSG, SENDMSG_NOOP, 0, 0,
null, 500);
}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 137b919..a4818ff 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -153,11 +153,11 @@
}
/*
- * Checks whether any music is active.
+ * Checks whether the specified stream type is active.
*
- * return true if any music tracks are active.
+ * return true if any track playing on this stream is active.
*/
- public static native boolean isMusicActive();
+ public static native boolean isStreamActive(int stream);
/*
* Sets a group generic audio configuration parameters. The use of these parameters
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index d3d2d29..83581d2 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -47,7 +47,7 @@
boolean shouldVibrate(int vibrateType);
- void setMode(int mode);
+ void setMode(int mode, IBinder cb);
int getMode();
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index e1b1776..e3b829b 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -170,10 +170,10 @@
}
-status_t AudioSystem::isMusicActive(bool* state) {
+status_t AudioSystem::isStreamActive(int stream, bool* state) {
const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
if (af == 0) return PERMISSION_DENIED;
- *state = af->isMusicActive();
+ *state = af->isStreamActive(stream);
return NO_ERROR;
}
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index 0eff205..fc42979 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -47,7 +47,7 @@
SET_MODE,
SET_MIC_MUTE,
GET_MIC_MUTE,
- IS_MUSIC_ACTIVE,
+ IS_STREAM_ACTIVE,
SET_PARAMETERS,
GET_PARAMETERS,
REGISTER_CLIENT,
@@ -286,11 +286,12 @@
return reply.readInt32();
}
- virtual bool isMusicActive() const
+ virtual bool isStreamActive(int stream) const
{
Parcel data, reply;
data.writeInterfaceToken(IAudioFlinger::getInterfaceDescriptor());
- remote()->transact(IS_MUSIC_ACTIVE, data, &reply);
+ data.writeInt32(stream);
+ remote()->transact(IS_STREAM_ACTIVE, data, &reply);
return reply.readInt32();
}
@@ -599,9 +600,10 @@
reply->writeInt32( getMicMute() );
return NO_ERROR;
} break;
- case IS_MUSIC_ACTIVE: {
+ case IS_STREAM_ACTIVE: {
CHECK_INTERFACE(IAudioFlinger, data, reply);
- reply->writeInt32( isMusicActive() );
+ int stream = data.readInt32();
+ reply->writeInt32( isStreamActive(stream) );
return NO_ERROR;
} break;
case SET_PARAMETERS: {
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index a55273d..6383f0c 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -20,6 +20,8 @@
#include "StagefrightRecorder.h"
+#include <media/stagefright/AudioSource.h>
+#include <media/stagefright/AMRWriter.h>
#include <media/stagefright/CameraSource.h>
#include <media/stagefright/MPEG4Writer.h>
#include <media/stagefright/MediaDebug.h>
@@ -146,7 +148,90 @@
return UNKNOWN_ERROR;
}
- if (mVideoSource == VIDEO_SOURCE_CAMERA) {
+ switch (mOutputFormat) {
+ case OUTPUT_FORMAT_DEFAULT:
+ case OUTPUT_FORMAT_THREE_GPP:
+ case OUTPUT_FORMAT_MPEG_4:
+ return startMPEG4Recording();
+
+ case OUTPUT_FORMAT_AMR_NB:
+ case OUTPUT_FORMAT_AMR_WB:
+ return startAMRRecording();
+
+ default:
+ return UNKNOWN_ERROR;
+ }
+}
+
+sp<MediaSource> StagefrightRecorder::createAMRAudioSource() {
+ uint32_t sampleRate =
+ mAudioEncoder == AUDIO_ENCODER_AMR_NB ? 8000 : 16000;
+
+ sp<AudioSource> audioSource =
+ new AudioSource(
+ mAudioSource,
+ sampleRate,
+ AudioSystem::CHANNEL_IN_MONO);
+
+ status_t err = audioSource->initCheck();
+
+ if (err != OK) {
+ return NULL;
+ }
+
+ sp<MetaData> encMeta = new MetaData;
+ encMeta->setCString(
+ kKeyMIMEType,
+ mAudioEncoder == AUDIO_ENCODER_AMR_NB
+ ? MEDIA_MIMETYPE_AUDIO_AMR_NB : MEDIA_MIMETYPE_AUDIO_AMR_WB);
+
+ encMeta->setInt32(kKeyChannelCount, 1);
+ encMeta->setInt32(kKeySampleRate, sampleRate);
+
+ OMXClient client;
+ CHECK_EQ(client.connect(), OK);
+
+ sp<MediaSource> audioEncoder =
+ OMXCodec::Create(client.interface(), encMeta,
+ true /* createEncoder */, audioSource);
+
+ return audioEncoder;
+}
+
+status_t StagefrightRecorder::startAMRRecording() {
+ if (mAudioSource == AUDIO_SOURCE_LIST_END
+ || mVideoSource != VIDEO_SOURCE_LIST_END) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (mOutputFormat == OUTPUT_FORMAT_AMR_NB
+ && mAudioEncoder != AUDIO_ENCODER_DEFAULT
+ && mAudioEncoder != AUDIO_ENCODER_AMR_NB) {
+ return UNKNOWN_ERROR;
+ } else if (mOutputFormat == OUTPUT_FORMAT_AMR_WB
+ && mAudioEncoder != AUDIO_ENCODER_AMR_WB) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<MediaSource> audioEncoder = createAMRAudioSource();
+
+ if (audioEncoder == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ CHECK(mOutputFd >= 0);
+ mWriter = new AMRWriter(dup(mOutputFd));
+ mWriter->addSource(audioEncoder);
+ mWriter->start();
+
+ return OK;
+}
+
+status_t StagefrightRecorder::startMPEG4Recording() {
+ mWriter = new MPEG4Writer(dup(mOutputFd));
+
+ if (mVideoSource == VIDEO_SOURCE_DEFAULT
+ || mVideoSource == VIDEO_SOURCE_CAMERA) {
CHECK(mCamera != NULL);
sp<CameraSource> cameraSource =
@@ -193,11 +278,20 @@
true /* createEncoder */, cameraSource);
CHECK(mOutputFd >= 0);
- mWriter = new MPEG4Writer(dup(mOutputFd));
mWriter->addSource(encoder);
- mWriter->start();
}
+ if (mAudioSource != AUDIO_SOURCE_LIST_END) {
+ sp<MediaSource> audioEncoder = createAMRAudioSource();
+
+ if (audioEncoder == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ mWriter->addSource(audioEncoder);
+ }
+
+ mWriter->start();
return OK;
}
@@ -235,7 +329,9 @@
}
status_t StagefrightRecorder::getMaxAmplitude(int *max) {
- return UNKNOWN_ERROR;
+ *max = 0;
+
+ return OK;
}
} // namespace android
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 56c4e0e..7ec412d 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -23,7 +23,8 @@
namespace android {
-class MPEG4Writer;
+struct MediaSource;
+struct MediaWriter;
struct StagefrightRecorder : public MediaRecorderBase {
StagefrightRecorder();
@@ -54,7 +55,7 @@
sp<ICamera> mCamera;
sp<ISurface> mPreviewSurface;
sp<IMediaPlayerClient> mListener;
- sp<MPEG4Writer> mWriter;
+ sp<MediaWriter> mWriter;
audio_source mAudioSource;
video_source mVideoSource;
@@ -66,6 +67,10 @@
String8 mParams;
int mOutputFd;
+ status_t startMPEG4Recording();
+ status_t startAMRRecording();
+ sp<MediaSource> createAMRAudioSource();
+
StagefrightRecorder(const StagefrightRecorder &);
StagefrightRecorder &operator=(const StagefrightRecorder &);
};
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
new file mode 100644
index 0000000..caff452
--- /dev/null
+++ b/media/libstagefright/AMRWriter.cpp
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/AMRWriter.h>
+
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+AMRWriter::AMRWriter(const char *filename)
+ : mFile(fopen(filename, "wb")),
+ mInitCheck(mFile != NULL ? OK : NO_INIT),
+ mStarted(false) {
+}
+
+AMRWriter::AMRWriter(int fd)
+ : mFile(fdopen(fd, "wb")),
+ mInitCheck(mFile != NULL ? OK : NO_INIT),
+ mStarted(false) {
+}
+
+AMRWriter::~AMRWriter() {
+ if (mStarted) {
+ stop();
+ }
+
+ if (mFile != NULL) {
+ fclose(mFile);
+ mFile = NULL;
+ }
+}
+
+status_t AMRWriter::initCheck() const {
+ return mInitCheck;
+}
+
+status_t AMRWriter::addSource(const sp<MediaSource> &source) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mInitCheck != OK) {
+ return mInitCheck;
+ }
+
+ if (mSource != NULL) {
+ // AMR files only support a single track of audio.
+ return UNKNOWN_ERROR;
+ }
+
+ sp<MetaData> meta = source->getFormat();
+
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ bool isWide = false;
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
+ isWide = true;
+ } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ int32_t channelCount;
+ int32_t sampleRate;
+ CHECK(meta->findInt32(kKeyChannelCount, &channelCount));
+ CHECK_EQ(channelCount, 1);
+ CHECK(meta->findInt32(kKeySampleRate, &sampleRate));
+ CHECK_EQ(sampleRate, (isWide ? 16000 : 8000));
+
+ mSource = source;
+
+ const char *kHeader = isWide ? "#!AMR-WB\n" : "#!AMR\n";
+ size_t n = strlen(kHeader);
+ if (fwrite(kHeader, 1, n, mFile) != n) {
+ return ERROR_IO;
+ }
+
+ return OK;
+}
+
+status_t AMRWriter::start() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mInitCheck != OK) {
+ return mInitCheck;
+ }
+
+ if (mStarted || mSource == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ status_t err = mSource->start();
+
+ if (err != OK) {
+ return err;
+ }
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ mReachedEOS = false;
+ mDone = false;
+
+ pthread_create(&mThread, &attr, ThreadWrapper, this);
+ pthread_attr_destroy(&attr);
+
+ mStarted = true;
+
+ return OK;
+}
+
+void AMRWriter::stop() {
+ {
+ Mutex::Autolock autoLock(mLock);
+
+ if (!mStarted) {
+ return;
+ }
+
+ mDone = true;
+ }
+
+ void *dummy;
+ pthread_join(mThread, &dummy);
+
+ mSource->stop();
+
+ mStarted = false;
+}
+
+// static
+void *AMRWriter::ThreadWrapper(void *me) {
+ static_cast<AMRWriter *>(me)->threadFunc();
+
+ return NULL;
+}
+
+void AMRWriter::threadFunc() {
+ for (;;) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mDone) {
+ break;
+ }
+
+ MediaBuffer *buffer;
+ status_t err = mSource->read(&buffer);
+
+ if (err != OK) {
+ break;
+ }
+
+ ssize_t n = fwrite(
+ (const uint8_t *)buffer->data() + buffer->range_offset(),
+ 1,
+ buffer->range_length(),
+ mFile);
+
+ buffer->release();
+ buffer = NULL;
+
+ if (n < (ssize_t)buffer->range_length()) {
+ break;
+ }
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ mReachedEOS = true;
+}
+
+bool AMRWriter::reachedEOS() {
+ Mutex::Autolock autoLock(mLock);
+ return mReachedEOS;
+}
+
+} // namespace android
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 26b9357..3813907 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -16,7 +16,9 @@
LOCAL_SRC_FILES += \
AMRExtractor.cpp \
+ AMRWriter.cpp \
AudioPlayer.cpp \
+ AudioSource.cpp \
AwesomePlayer.cpp \
CachingDataSource.cpp \
CameraSource.cpp \
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
new file mode 100644
index 0000000..edabaf9
--- /dev/null
+++ b/media/libstagefright/AudioSource.cpp
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/AudioSource.h>
+
+#include <media/AudioRecord.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+AudioSource::AudioSource(
+ int inputSource, uint32_t sampleRate, uint32_t channels)
+ : mRecord(new AudioRecord(
+ inputSource, sampleRate, AudioSystem::PCM_16_BIT, channels)),
+ mInitCheck(mRecord->initCheck()),
+ mStarted(false),
+ mGroup(NULL) {
+}
+
+AudioSource::~AudioSource() {
+ if (mStarted) {
+ stop();
+ }
+
+ delete mRecord;
+ mRecord = NULL;
+}
+
+status_t AudioSource::initCheck() const {
+ return mInitCheck;
+}
+
+status_t AudioSource::start(MetaData *params) {
+ if (mStarted) {
+ return UNKNOWN_ERROR;
+ }
+
+ status_t err = mRecord->start();
+
+ if (err == OK) {
+ mGroup = new MediaBufferGroup;
+ mGroup->add_buffer(new MediaBuffer(kMaxBufferSize));
+
+ mStarted = true;
+ }
+
+ return err;
+}
+
+status_t AudioSource::stop() {
+ if (!mStarted) {
+ return UNKNOWN_ERROR;
+ }
+
+ mRecord->stop();
+
+ delete mGroup;
+ mGroup = NULL;
+
+ mStarted = false;
+
+ return OK;
+}
+
+sp<MetaData> AudioSource::getFormat() {
+ sp<MetaData> meta = new MetaData;
+ meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+ meta->setInt32(kKeySampleRate, mRecord->getSampleRate());
+ meta->setInt32(kKeyChannelCount, mRecord->channelCount());
+ meta->setInt32(kKeyMaxInputSize, kMaxBufferSize);
+
+ return meta;
+}
+
+status_t AudioSource::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ *out = NULL;
+
+ MediaBuffer *buffer;
+ CHECK_EQ(mGroup->acquire_buffer(&buffer), OK);
+
+ uint32_t numFramesRecorded;
+ mRecord->getPosition(&numFramesRecorded);
+
+ buffer->meta_data()->setInt64(
+ kKeyTime,
+ (1000000ll * numFramesRecorded) / mRecord->getSampleRate()
+ - mRecord->latency() * 1000);
+
+ ssize_t n = mRecord->read(buffer->data(), buffer->size());
+
+ if (n < 0) {
+ buffer->release();
+ buffer = NULL;
+
+ return (status_t)n;
+ }
+
+ buffer->set_range(0, n);
+
+ *out = buffer;
+
+ return OK;
+}
+
+} // namespace android
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 367459f..aee4d15 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -92,9 +92,11 @@
mTracks.clear();
}
-void MPEG4Writer::addSource(const sp<MediaSource> &source) {
+status_t MPEG4Writer::addSource(const sp<MediaSource> &source) {
Track *track = new Track(this, source);
mTracks.push_back(track);
+
+ return OK;
}
status_t MPEG4Writer::start() {
diff --git a/media/sdutils/sdutil.cpp b/media/sdutils/sdutil.cpp
index 322f743..a61cccb 100644
--- a/media/sdutils/sdutil.cpp
+++ b/media/sdutils/sdutil.cpp
@@ -134,6 +134,12 @@
gMountService->unmountSecureContainer(sId);
}
+static void asec_rename(const char *oldId, const char *newId) {
+ String16 sOldId(oldId);
+ String16 sNewId(newId);
+ gMountService->renameSecureContainer(sOldId, sNewId);
+}
+
static int asec_path(const char *id) {
String16 sId(id);
gMountService->getSecureContainerPath(sId);
@@ -212,7 +218,13 @@
} else if (!strcmp(argument, "destroy")) {
return android::asec_destroy(id);
} else if (!strcmp(argument, "mount")) {
- return android::asec_mount(id, argv[4], atoi(argv[5]));
+ if (argc == 6)
+ return android::asec_mount(id, argv[4], atoi(argv[5]));
+ } else if (!strcmp(argument, "rename")) {
+ if (argc == 5) {
+ android::asec_rename(id, argv[4]);
+ return 0;
+ }
} else if (!strcmp(argument, "unmount")) {
android::asec_unmount(id);
return 0;
@@ -233,6 +245,7 @@
" sdutil asec destroy <id>\n"
" sdutil asec mount <id> <key> <ownerUid>\n"
" sdutil asec unmount <id>\n"
+ " sdutil asec rename <oldId, newId>\n"
" sdutil asec path <id>\n"
);
return -1;
diff --git a/opengl/libagl/texture.cpp b/opengl/libagl/texture.cpp
index 13d078e..2875c13 100644
--- a/opengl/libagl/texture.cpp
+++ b/opengl/libagl/texture.cpp
@@ -583,7 +583,7 @@
static __attribute__((noinline))
-void set_depth_and_fog(ogles_context_t* c, GLint z)
+void set_depth_and_fog(ogles_context_t* c, GGLfixed z)
{
const uint32_t enables = c->rasterizer.state.enables;
// we need to compute Zw
@@ -592,8 +592,8 @@
GGLfixed Zw;
GGLfixed n = gglFloatToFixed(c->transforms.vpt.zNear);
GGLfixed f = gglFloatToFixed(c->transforms.vpt.zFar);
- if (z<=0) Zw = n;
- else if (z>=1) Zw = f;
+ if (z<=0) Zw = n;
+ else if (z>=0x10000) Zw = f;
else Zw = gglMulAddx(z, (f-n), n);
if (enables & GGL_ENABLE_FOG) {
// set up fog if needed...
@@ -836,7 +836,7 @@
c->rasterizer.procs.texCoord2i(c, s0, t0);
const uint32_t enables = c->rasterizer.state.enables;
if (ggl_unlikely(enables & (GGL_ENABLE_DEPTH_TEST|GGL_ENABLE_FOG)))
- set_depth_and_fog(c, z);
+ set_depth_and_fog(c, gglIntToFixed(z));
c->rasterizer.procs.color4xv(c, c->currentColorClamped.v);
c->rasterizer.procs.disable(c, GGL_W_LERP);
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 5a8d35f..4417d7b 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -828,6 +828,28 @@
info.getExtraInfo());
}
+ NetworkStateTracker newNet = tryFailover(prevNetType);
+ if (newNet != null) {
+ NetworkInfo switchTo = newNet.getNetworkInfo();
+ intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+ } else {
+ intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+ }
+ // do this before we broadcast the change
+ handleConnectivityChange();
+
+ sendStickyBroadcast(intent);
+ /*
+ * If the failover network is already connected, then immediately send
+ * out a followup broadcast indicating successful failover
+ */
+ if (newNet != null && newNet.getNetworkInfo().isConnected()) {
+ sendConnectedBroadcast(newNet.getNetworkInfo());
+ }
+ }
+
+ // returns -1 if no failover available
+ private NetworkStateTracker tryFailover(int prevNetType) {
/*
* If this is a default network, check if other defaults are available
* or active
@@ -840,8 +862,7 @@
int newType = -1;
int newPriority = -1;
- for (int checkType=0; checkType <=
- ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
+ for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
if (checkType == prevNetType) continue;
if (mNetAttributes[checkType] == null) continue;
if (mNetAttributes[checkType].isDefault()) {
@@ -884,29 +905,13 @@
switchTo.getTypeName());
}
}
- intent.putExtra(ConnectivityManager.
- EXTRA_OTHER_NETWORK_INFO, switchTo);
} else {
- intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
- true);
newNet.reconnect();
}
- } else {
- intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY,
- true);
}
}
- // do this before we broadcast the change
- handleConnectivityChange();
-
- sendStickyBroadcast(intent);
- /*
- * If the failover network is already connected, then immediately send
- * out a followup broadcast indicating successful failover
- */
- if (newNet != null && newNet.getNetworkInfo().isConnected())
- sendConnectedBroadcast(newNet.getNetworkInfo());
+ return newNet;
}
private void sendConnectedBroadcast(NetworkInfo info) {
@@ -964,7 +969,25 @@
intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
info.setFailover(false);
}
+
+ NetworkStateTracker newNet = tryFailover(info.getType());
+ if (newNet != null) {
+ NetworkInfo switchTo = newNet.getNetworkInfo();
+ intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+ } else {
+ intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+ }
+ // do this before we broadcast the change
+ handleConnectivityChange();
+
sendStickyBroadcast(intent);
+ /*
+ * If the failover network is already connected, then immediately send
+ * out a followup broadcast indicating successful failover
+ */
+ if (newNet != null && newNet.getNetworkInfo().isConnected()) {
+ sendConnectedBroadcast(newNet.getNetworkInfo());
+ }
}
private void sendStickyBroadcast(Intent intent) {
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 0cee31d..353da12 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -1056,6 +1056,11 @@
mConnector.doCommand(cmd);
}
+ public void renameSecureContainer(String oldId, String newId) throws IllegalStateException {
+ String cmd = String.format("rename_asec %s %s", oldId, newId);
+ mConnector.doCommand(cmd);
+ }
+
public String getSecureContainerPath(String id) throws IllegalStateException {
ArrayList<String> rsp = mConnector.doCommand("asec_path " + id);
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 8594e44..b34b50a 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -78,7 +78,7 @@
*
* @param context Binder context for this service
*/
- private NetworkManagementService(Context context) {
+ public NetworkManagementService(Context context) {
mContext = context;
mObservers = new ArrayList<INetworkManagementEventObserver>();
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index cafc804..1307972 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -2855,9 +2855,8 @@
// room left on the data partition, or a ZipException if the package
// file is malformed.
//
- private int cachePackageSharedLibsForAbiLI( PackageParser.Package pkg,
- File dataPath, File scanFile, String cpuAbi)
- throws IOException, ZipException {
+ private int cachePackageSharedLibsForAbiLI(PackageParser.Package pkg,
+ File dataPath, File scanFile, String cpuAbi) throws IOException, ZipException {
File sharedLibraryDir = new File(dataPath.getPath() + "/lib");
final String apkLib = "lib/";
final int apkLibLen = apkLib.length();
@@ -2935,7 +2934,7 @@
if (mInstaller == null) {
sharedLibraryDir.mkdir();
}
- cacheSharedLibLI(pkg, zipFile, entry, sharedLibraryDir,
+ cacheNativeBinaryLI(pkg, zipFile, entry, sharedLibraryDir,
sharedLibraryFile);
}
}
@@ -2948,6 +2947,54 @@
return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
}
+ // Find the gdbserver executable program in a package at
+ // lib/<cpuAbi>/gdbserver and copy it to /data/data/<name>/lib/gdbserver
+ //
+ // Returns PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES on success,
+ // or PACKAGE_INSTALL_NATIVE_NO_LIBRARIES otherwise.
+ //
+ private int cachePackageGdbServerLI(PackageParser.Package pkg,
+ File dataPath, File scanFile, String cpuAbi) throws IOException, ZipException {
+ File installGdbServerDir = new File(dataPath.getPath() + "/lib");
+ final String GDBSERVER = "gdbserver";
+ final String apkGdbServerPath = "lib/" + cpuAbi + "/" + GDBSERVER;
+
+ ZipFile zipFile = new ZipFile(scanFile);
+ Enumeration<ZipEntry> entries =
+ (Enumeration<ZipEntry>) zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ // skip directories
+ if (entry.isDirectory()) {
+ continue;
+ }
+ String entryName = entry.getName();
+
+ if (!entryName.equals(apkGdbServerPath)) {
+ continue;
+ }
+
+ String installGdbServerPath = installGdbServerDir.getPath() +
+ "/" + GDBSERVER;
+ File installGdbServerFile = new File(installGdbServerPath);
+ if (! installGdbServerFile.exists() ||
+ installGdbServerFile.length() != entry.getSize() ||
+ installGdbServerFile.lastModified() != entry.getTime()) {
+ if (Config.LOGD) {
+ Log.d(TAG, "Caching gdbserver " + entry.getName());
+ }
+ if (mInstaller == null) {
+ installGdbServerDir.mkdir();
+ }
+ cacheNativeBinaryLI(pkg, zipFile, entry, installGdbServerDir,
+ installGdbServerFile);
+ }
+ return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
+ }
+ return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
+ }
+
// extract shared libraries stored in the APK as lib/<cpuAbi>/lib<name>.so
// and copy them to /data/data/<appname>/lib.
//
@@ -2957,7 +3004,7 @@
//
private int cachePackageSharedLibsLI(PackageParser.Package pkg,
File dataPath, File scanFile) {
- final String cpuAbi = Build.CPU_ABI;
+ String cpuAbi = Build.CPU_ABI;
try {
int result = cachePackageSharedLibsForAbiLI(pkg, dataPath, scanFile, cpuAbi);
@@ -2968,7 +3015,7 @@
//
// only scan the package twice in case of ABI mismatch
if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
- String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2",null);
+ final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2",null);
if (cpuAbi2 != null) {
result = cachePackageSharedLibsForAbiLI(pkg, dataPath, scanFile, cpuAbi2);
}
@@ -2977,6 +3024,20 @@
Log.w(TAG,"Native ABI mismatch from package file");
return PackageManager.INSTALL_FAILED_INVALID_APK;
}
+
+ if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
+ cpuAbi = cpuAbi2;
+ }
+ }
+
+ // for debuggable packages, also extract gdbserver from lib/<abi>
+ // into /data/data/<appname>/lib too.
+ if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES &&
+ (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ int result2 = cachePackageGdbServerLI(pkg, dataPath, scanFile, cpuAbi);
+ if (result2 == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_NATIVE_DEBUGGABLE;
+ }
}
} catch (ZipException e) {
Log.w(TAG, "Failed to extract data from package file", e);
@@ -2988,26 +3049,27 @@
return PackageManager.INSTALL_SUCCEEDED;
}
- private void cacheSharedLibLI(PackageParser.Package pkg,
+ private void cacheNativeBinaryLI(PackageParser.Package pkg,
ZipFile zipFile, ZipEntry entry,
- File sharedLibraryDir,
- File sharedLibraryFile) throws IOException {
+ File binaryDir,
+ File binaryFile) throws IOException {
InputStream inputStream = zipFile.getInputStream(entry);
try {
- File tempFile = File.createTempFile("tmp", "tmp", sharedLibraryDir);
+ File tempFile = File.createTempFile("tmp", "tmp", binaryDir);
String tempFilePath = tempFile.getPath();
- // XXX package manager can't change owner, so the lib files for
+ // XXX package manager can't change owner, so the executable files for
// now need to be left as world readable and owned by the system.
if (! FileUtils.copyToFile(inputStream, tempFile) ||
! tempFile.setLastModified(entry.getTime()) ||
FileUtils.setPermissions(tempFilePath,
FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP
+ |FileUtils.S_IXUSR|FileUtils.S_IXGRP|FileUtils.S_IXOTH
|FileUtils.S_IROTH, -1, -1) != 0 ||
- ! tempFile.renameTo(sharedLibraryFile)) {
+ ! tempFile.renameTo(binaryFile)) {
// Failed to properly write file.
tempFile.delete();
- throw new IOException("Couldn't create cached shared lib "
- + sharedLibraryFile + " in " + sharedLibraryDir);
+ throw new IOException("Couldn't create cached binary "
+ + binaryFile + " in " + binaryDir);
}
} finally {
inputStream.close();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6b3f433..22447ed 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -249,6 +249,14 @@
}
try {
+ Log.i(TAG, "NetworkManagement Service");
+ ServiceManager.addService(
+ Context.NETWORKMANAGEMENT_SERVICE, new NetworkManagementService(context));
+ } catch (Throwable e) {
+ Log.e(TAG, "Failure starting NetworkManagement Service", e);
+ }
+
+ try {
Log.i(TAG, "Connectivity Service");
connectivity = ConnectivityService.getInstance(context);
ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
diff --git a/telephony/java/com/android/internal/telephony/MccTable.java b/telephony/java/com/android/internal/telephony/MccTable.java
index e25eb7b..7f62169 100644
--- a/telephony/java/com/android/internal/telephony/MccTable.java
+++ b/telephony/java/com/android/internal/telephony/MccTable.java
@@ -590,7 +590,7 @@
if (mcc != 0) {
setTimezoneFromMccIfNeeded(phone, mcc);
setLocaleFromMccIfNeeded(phone, mcc);
- setWifiChannelsFromMccIfNeeded(phone, mcc);
+ setWifiChannelsFromMcc(phone, mcc);
}
try {
Configuration config = ActivityManagerNative.getDefault().getConfiguration();
@@ -646,20 +646,14 @@
* @param phone PhoneBase to act on (get context from).
* @param mcc Mobile Country Code of the SIM or SIM-like entity (build prop on CDMA)
*/
- private static void setWifiChannelsFromMccIfNeeded(PhoneBase phone, int mcc) {
+ private static void setWifiChannelsFromMcc(PhoneBase phone, int mcc) {
int wifiChannels = MccTable.wifiChannelsForMcc(mcc);
if (wifiChannels != 0) {
Context context = phone.getContext();
- // only set to this default if the user hasn't manually set it
- try {
- Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS);
- } catch (Settings.SettingNotFoundException e) {
- Log.d(LOG_TAG, "WIFI_NUM_ALLOWED_CHANNESL set to " + wifiChannels);
- WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- // don't persist
- wM.setNumAllowedChannels(wifiChannels, false);
- }
+ Log.d(LOG_TAG, "WIFI_NUM_ALLOWED_CHANNELS set to " + wifiChannels);
+ WifiManager wM = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ //persist
+ wM.setNumAllowedChannels(wifiChannels, true);
}
}
}
diff --git a/tests/CoreTests/android/content/SyncQueueTest.java b/tests/CoreTests/android/content/SyncQueueTest.java
new file mode 100644
index 0000000..5f4ab78
--- /dev/null
+++ b/tests/CoreTests/android/content/SyncQueueTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2007 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.test.AndroidTestCase;
+import android.test.RenamingDelegatingContext;
+import android.test.mock.MockContext;
+import android.test.mock.MockContentResolver;
+import android.accounts.Account;
+import android.os.Bundle;
+import android.os.SystemClock;
+
+public class SyncQueueTest extends AndroidTestCase {
+ private static final Account ACCOUNT1 = new Account("test.account1", "test.type1");
+ private static final Account ACCOUNT2 = new Account("test.account2", "test.type2");
+ private static final String AUTHORITY1 = "test.authority1";
+ private static final String AUTHORITY2 = "test.authority2";
+ private static final String AUTHORITY3 = "test.authority3";
+
+ private SyncStorageEngine mSettings;
+ private SyncQueue mSyncQueue;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ MockContentResolver mockResolver = new MockContentResolver();
+ mSettings = SyncStorageEngine.newTestInstance(new TestContext(mockResolver, getContext()));
+ mSyncQueue = new SyncQueue(mSettings);
+ }
+
+ public void testSyncQueueOrder() throws Exception {
+ final SyncOperation op1 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
+ final SyncOperation op2 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
+ final SyncOperation op3 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
+ final SyncOperation op4 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("4"), 60);
+ final SyncOperation op5 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
+ final SyncOperation op6 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
+ op6.expedited = true;
+
+ mSyncQueue.add(op1);
+ mSyncQueue.add(op2);
+ mSyncQueue.add(op3);
+ mSyncQueue.add(op4);
+ mSyncQueue.add(op5);
+ mSyncQueue.add(op6);
+
+ long now = SystemClock.elapsedRealtime() + 200;
+
+ assertEquals(op6, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op6);
+
+ assertEquals(op1, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op1);
+
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op4);
+
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op5);
+
+ assertEquals(op2, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op2);
+
+ assertEquals(op3, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op3);
+ }
+
+ public void testOrderWithBackoff() throws Exception {
+ final SyncOperation op1 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("1"), 0);
+ final SyncOperation op2 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("2"), 100);
+ final SyncOperation op3 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("3"), 150);
+ final SyncOperation op4 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY3, newTestBundle("4"), 60);
+ final SyncOperation op5 = new SyncOperation(
+ ACCOUNT1, SyncStorageEngine.SOURCE_USER, AUTHORITY1, newTestBundle("5"), 80);
+ final SyncOperation op6 = new SyncOperation(
+ ACCOUNT2, SyncStorageEngine.SOURCE_USER, AUTHORITY2, newTestBundle("6"), 0);
+ op6.expedited = true;
+
+ mSyncQueue.add(op1);
+ mSyncQueue.add(op2);
+ mSyncQueue.add(op3);
+ mSyncQueue.add(op4);
+ mSyncQueue.add(op5);
+ mSyncQueue.add(op6);
+
+ long now = SystemClock.elapsedRealtime() + 200;
+
+ assertEquals(op6, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op6);
+
+ assertEquals(op1, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op1);
+
+ mSettings.setBackoff(ACCOUNT2, AUTHORITY3, now + 200, 5);
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+
+ mSettings.setBackoff(ACCOUNT2, AUTHORITY3, SyncStorageEngine.NOT_IN_BACKOFF_MODE, 0);
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+
+ mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, now + 200);
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+
+ mSettings.setDelayUntilTime(ACCOUNT2, AUTHORITY3, 0);
+ assertEquals(op4, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op4);
+
+ assertEquals(op5, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op5);
+
+ assertEquals(op2, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op2);
+
+ assertEquals(op3, mSyncQueue.nextReadyToRun(now));
+ mSyncQueue.remove(op3);
+ }
+
+ Bundle newTestBundle(String val) {
+ Bundle bundle = new Bundle();
+ bundle.putString("test", val);
+ return bundle;
+ }
+
+ static class TestContext extends ContextWrapper {
+ ContentResolver mResolver;
+
+ public TestContext(ContentResolver resolver, Context realContext) {
+ super(new RenamingDelegatingContext(new MockContext(), realContext, "test."));
+ mResolver = resolver;
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mResolver;
+ }
+ }
+}