Merge "Updated action bar/CAB assets Bug: 5156319 5076695"
diff --git a/api/current.txt b/api/current.txt
index dfc17ba..6daf77e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10681,6 +10681,7 @@
method public void setOnVideoSizeChangedListener(android.media.MediaPlayer.OnVideoSizeChangedListener);
method public void setScreenOnWhilePlaying(boolean);
method public void setTexture(android.graphics.SurfaceTexture);
+ method public void setSurface(android.view.Surface);
method public void setVolume(float, float);
method public void setWakeMode(android.content.Context, int);
method public void start() throws java.lang.IllegalStateException;
@@ -16057,7 +16058,7 @@
public final class ContactsContract {
ctor public ContactsContract();
- field public static final java.lang.String ALLOW_PROFILE = "allow_profile";
+ method public static boolean isProfileId(long);
field public static final java.lang.String AUTHORITY = "com.android.contacts";
field public static final android.net.Uri AUTHORITY_URI;
field public static final java.lang.String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
@@ -16584,6 +16585,16 @@
field public static final android.net.Uri CONTENT_RAW_CONTACTS_URI;
field public static final android.net.Uri CONTENT_URI;
field public static final android.net.Uri CONTENT_VCARD_URI;
+ field public static final long MIN_ID = 9223372034707292160L; // 0x7fffffff80000000L
+ }
+
+ public static final class ContactsContract.ProfileSyncState implements android.provider.SyncStateContract.Columns {
+ method public static byte[] get(android.content.ContentProviderClient, android.accounts.Account) throws android.os.RemoteException;
+ method public static android.util.Pair<android.net.Uri, byte[]> getWithUri(android.content.ContentProviderClient, android.accounts.Account) throws android.os.RemoteException;
+ method public static android.content.ContentProviderOperation newSetOperation(android.accounts.Account, byte[]);
+ method public static void set(android.content.ContentProviderClient, android.accounts.Account, byte[]) throws android.os.RemoteException;
+ field public static final java.lang.String CONTENT_DIRECTORY = "syncstate";
+ field public static final android.net.Uri CONTENT_URI;
}
public static final class ContactsContract.QuickContact {
@@ -16682,6 +16693,7 @@
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/status-update";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/status-update";
field public static final android.net.Uri CONTENT_URI;
+ field public static final android.net.Uri PROFILE_CONTENT_URI;
}
public static final class ContactsContract.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns {
@@ -22514,6 +22526,7 @@
}
public class Surface implements android.os.Parcelable {
+ ctor public Surface(android.graphics.SurfaceTexture);
method public int describeContents();
method public boolean isValid();
method public android.graphics.Canvas lockCanvas(android.graphics.Rect) throws java.lang.IllegalArgumentException, android.view.Surface.OutOfResourcesException;
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 5321c6a..1f2b342 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -125,19 +125,6 @@
public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
/**
- * An optional URI parameter for selection queries that instructs the
- * provider to allow the user's personal profile contact entry (if any)
- * to appear in a list of contact results. It is only useful when issuing
- * a query that may retrieve more than one contact. If present, the user's
- * profile will always be the first entry returned. The default value is
- * false.
- *
- * Specifying this parameter will result in a security error if the calling
- * application does not have android.permission.READ_PROFILE permission.
- */
- public static final String ALLOW_PROFILE = "allow_profile";
-
- /**
* Query parameter that should be used by the client to access a specific
* {@link Directory}. The parameter value should be the _ID of the corresponding
* directory, e.g.
@@ -557,7 +544,7 @@
}
/**
- * A table provided for sync adapters to use for storing private sync state data.
+ * A table provided for sync adapters to use for storing private sync state data for contacts.
*
* @see SyncStateContract
*/
@@ -608,6 +595,60 @@
}
}
+
+ /**
+ * A table provided for sync adapters to use for storing private sync state data for the
+ * user's personal profile.
+ *
+ * @see SyncStateContract
+ */
+ public static final class ProfileSyncState implements SyncStateContract.Columns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private ProfileSyncState() {}
+
+ public static final String CONTENT_DIRECTORY =
+ SyncStateContract.Constants.CONTENT_DIRECTORY;
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.withAppendedPath(Profile.CONTENT_URI, CONTENT_DIRECTORY);
+
+ /**
+ * @see android.provider.SyncStateContract.Helpers#get
+ */
+ public static byte[] get(ContentProviderClient provider, Account account)
+ throws RemoteException {
+ return SyncStateContract.Helpers.get(provider, CONTENT_URI, account);
+ }
+
+ /**
+ * @see android.provider.SyncStateContract.Helpers#get
+ */
+ public static Pair<Uri, byte[]> getWithUri(ContentProviderClient provider, Account account)
+ throws RemoteException {
+ return SyncStateContract.Helpers.getWithUri(provider, CONTENT_URI, account);
+ }
+
+ /**
+ * @see android.provider.SyncStateContract.Helpers#set
+ */
+ public static void set(ContentProviderClient provider, Account account, byte[] data)
+ throws RemoteException {
+ SyncStateContract.Helpers.set(provider, CONTENT_URI, account, data);
+ }
+
+ /**
+ * @see android.provider.SyncStateContract.Helpers#newSetOperation
+ */
+ public static ContentProviderOperation newSetOperation(Account account, byte[] data) {
+ return SyncStateContract.Helpers.newSetOperation(CONTENT_URI, account, data);
+ }
+ }
+
/**
* Generic columns for use by sync adapters. The specific functions of
* these columns are private to the sync adapter. Other clients of the API
@@ -1875,8 +1916,8 @@
* Constants for the user's profile data, which is represented as a single contact on
* the device that represents the user. The profile contact is not aggregated
* together automatically in the same way that normal contacts are; instead, each
- * account on the device may contribute a single raw contact representing the user's
- * personal profile data from that source.
+ * account (including data set, if applicable) on the device may contribute a single
+ * raw contact representing the user's personal profile data from that source.
* </p>
* <p>
* Access to the profile entry through these URIs (or incidental access to parts of
@@ -1950,6 +1991,31 @@
*/
public static final Uri CONTENT_RAW_CONTACTS_URI = Uri.withAppendedPath(CONTENT_URI,
"raw_contacts");
+
+ /**
+ * The minimum ID for any entity that belongs to the profile. This essentially
+ * defines an ID-space in which profile data is stored, and is used by the provider
+ * to determine whether a request via a non-profile-specific URI should be directed
+ * to the profile data rather than general contacts data, along with all the special
+ * permission checks that entails.
+ *
+ * Callers may use {@link #isProfileId} to check whether a specific ID falls into
+ * the set of data intended for the profile.
+ */
+ public static final long MIN_ID = Long.MAX_VALUE - (long) Integer.MAX_VALUE;
+ }
+
+ /**
+ * This method can be used to identify whether the given ID is associated with profile
+ * data. It does not necessarily indicate that the ID is tied to valid data, merely
+ * that accessing data using this ID will result in profile access checks and will only
+ * return data from the profile.
+ *
+ * @param id The ID to check.
+ * @return Whether the ID is associated with profile data.
+ */
+ public static boolean isProfileId(long id) {
+ return id >= Profile.MIN_ID;
}
protected interface RawContactsColumns {
@@ -4542,6 +4608,12 @@
* either.
* </p>
* <p>
+ * Inserting or updating a status update for the user's profile requires either using
+ * the {@link #DATA_ID} to identify the data row to attach the update to, or
+ * {@link StatusUpdates#PROFILE_CONTENT_URI} to ensure that the change is scoped to the
+ * profile.
+ * </p>
+ * <p>
* You cannot use {@link ContentResolver#update} to change a status, but
* {@link ContentResolver#insert} will replace the latests status if it already
* exists.
@@ -4687,6 +4759,12 @@
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "status_updates");
/**
+ * The content:// style URI for this table, specific to the user's profile.
+ */
+ public static final Uri PROFILE_CONTENT_URI =
+ Uri.withAppendedPath(Profile.CONTENT_URI, "status_updates");
+
+ /**
* Gets the resource ID for the proper presence icon.
*
* @param status the status to get the icon for
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index 89b6f32..f8d0142 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -390,10 +390,10 @@
audioTrack.play();
}
int count = 0;
- while (count < bufferCopy.mLength) {
+ while (count < bufferCopy.mBytes.length) {
// Note that we don't take bufferCopy.mOffset into account because
// it is guaranteed to be 0.
- int written = audioTrack.write(bufferCopy.mBytes, count, bufferCopy.mLength);
+ int written = audioTrack.write(bufferCopy.mBytes, count, bufferCopy.mBytes.length);
if (written <= 0) {
break;
}
@@ -453,7 +453,7 @@
}
final AudioTrack audioTrack = params.mAudioTrack;
- final int bytesPerFrame = getBytesPerFrame(params.mAudioFormat);
+ final int bytesPerFrame = params.mBytesPerFrame;
final int lengthInBytes = params.mBytesWritten;
final int lengthInFrames = lengthInBytes / bytesPerFrame;
@@ -511,16 +511,6 @@
return 0;
}
- static int getBytesPerFrame(int audioFormat) {
- if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
- return 1;
- } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
- return 2;
- }
-
- return -1;
- }
-
private static void setupVolume(AudioTrack audioTrack, float volume, float pan) {
float vol = clip(volume, 0.0f, 1.0f);
float panning = clip(pan, -1.0f, 1.0f);
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 7dbf1ac..0cca06ac 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -85,6 +85,7 @@
// Note that mLogger.mError might be true too at this point.
mLogger.onStopped();
+ SynthesisMessageParams token = null;
synchronized (mStateLock) {
if (mStopped) {
Log.w(TAG, "stop() called twice");
@@ -97,9 +98,19 @@
// In all other cases, mAudioTrackHandler.stop() will
// result in onComplete being called.
mLogger.onWriteData();
+ } else {
+ token = mToken;
}
mStopped = true;
}
+
+ if (token != null) {
+ // This might result in the synthesis thread being woken up, at which
+ // point it will write an additional buffer to the token - but we
+ // won't worry about that because the audio playback queue will be cleared
+ // soon after (see SynthHandler#stop(String).
+ token.clearBuffers();
+ }
}
@Override
@@ -155,18 +166,22 @@
+ length + " bytes)");
}
+ SynthesisMessageParams token = null;
synchronized (mStateLock) {
if (mToken == null || mStopped) {
return TextToSpeech.ERROR;
}
-
- // Sigh, another copy.
- final byte[] bufferCopy = new byte[length];
- System.arraycopy(buffer, offset, bufferCopy, 0, length);
- mToken.addBuffer(bufferCopy);
- mAudioTrackHandler.enqueueSynthesisDataAvailable(mToken);
+ token = mToken;
}
+ // Sigh, another copy.
+ final byte[] bufferCopy = new byte[length];
+ System.arraycopy(buffer, offset, bufferCopy, 0, length);
+ // Might block on mToken.this, if there are too many buffers waiting to
+ // be consumed.
+ token.addBuffer(bufferCopy);
+ mAudioTrackHandler.enqueueSynthesisDataAvailable(token);
+
mLogger.onEngineDataReceived();
return TextToSpeech.SUCCESS;
@@ -176,6 +191,7 @@
public int done() {
if (DBG) Log.d(TAG, "done()");
+ SynthesisMessageParams token = null;
synchronized (mStateLock) {
if (mDone) {
Log.w(TAG, "Duplicate call to done()");
@@ -188,9 +204,12 @@
return TextToSpeech.ERROR;
}
- mAudioTrackHandler.enqueueSynthesisDone(mToken);
- mLogger.onEngineComplete();
+ token = mToken;
}
+
+ mAudioTrackHandler.enqueueSynthesisDone(token);
+ mLogger.onEngineComplete();
+
return TextToSpeech.SUCCESS;
}
diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java
index 7da5daa..3e905d6 100644
--- a/core/java/android/speech/tts/SynthesisMessageParams.java
+++ b/core/java/android/speech/tts/SynthesisMessageParams.java
@@ -15,6 +15,7 @@
*/
package android.speech.tts;
+import android.media.AudioFormat;
import android.media.AudioTrack;
import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
@@ -24,6 +25,8 @@
* Params required to play back a synthesis request.
*/
final class SynthesisMessageParams extends MessageParams {
+ private static final long MAX_UNCONSUMED_AUDIO_MS = 500;
+
final int mStreamType;
final int mSampleRateInHz;
final int mAudioFormat;
@@ -32,10 +35,16 @@
final float mPan;
final EventLogger mLogger;
+ final int mBytesPerFrame;
+
volatile AudioTrack mAudioTrack;
- // Not volatile, accessed only from the synthesis thread.
- int mBytesWritten;
+ // Written by the synthesis thread, but read on the audio playback
+ // thread.
+ volatile int mBytesWritten;
+ // Not volatile, accessed only from the audio playback thread.
int mAudioBufferSize;
+ // Always synchronized on "this".
+ int mUnconsumedBytes;
private final LinkedList<ListEntry> mDataBufferList = new LinkedList<ListEntry>();
@@ -53,6 +62,8 @@
mPan = pan;
mLogger = logger;
+ mBytesPerFrame = getBytesPerFrame(mAudioFormat) * mChannelCount;
+
// initially null.
mAudioTrack = null;
mBytesWritten = 0;
@@ -64,18 +75,36 @@
return TYPE_SYNTHESIS;
}
- synchronized void addBuffer(byte[] buffer, int offset, int length) {
- mDataBufferList.add(new ListEntry(buffer, offset, length));
+ synchronized void addBuffer(byte[] buffer) {
+ long unconsumedAudioMs = 0;
+
+ while ((unconsumedAudioMs = getUnconsumedAudioLengthMs()) > MAX_UNCONSUMED_AUDIO_MS) {
+ try {
+ wait();
+ } catch (InterruptedException ie) {
+ return;
+ }
+ }
+
+ mDataBufferList.add(new ListEntry(buffer));
+ mUnconsumedBytes += buffer.length;
}
- synchronized void addBuffer(byte[] buffer) {
- mDataBufferList.add(new ListEntry(buffer, 0, buffer.length));
+ synchronized void clearBuffers() {
+ mDataBufferList.clear();
+ mUnconsumedBytes = 0;
+ notifyAll();
}
synchronized ListEntry getNextBuffer() {
- return mDataBufferList.poll();
- }
+ ListEntry entry = mDataBufferList.poll();
+ if (entry != null) {
+ mUnconsumedBytes -= entry.mBytes.length;
+ notifyAll();
+ }
+ return entry;
+ }
void setAudioTrack(AudioTrack audioTrack) {
mAudioTrack = audioTrack;
@@ -85,15 +114,29 @@
return mAudioTrack;
}
+ // Must be called synchronized on this.
+ private long getUnconsumedAudioLengthMs() {
+ final int unconsumedFrames = mUnconsumedBytes / mBytesPerFrame;
+ final long estimatedTimeMs = unconsumedFrames * 1000 / mSampleRateInHz;
+
+ return estimatedTimeMs;
+ }
+
+ private static int getBytesPerFrame(int audioFormat) {
+ if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
+ return 1;
+ } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
+ return 2;
+ }
+
+ return -1;
+ }
+
static final class ListEntry {
final byte[] mBytes;
- final int mOffset;
- final int mLength;
- ListEntry(byte[] bytes, int offset, int length) {
+ ListEntry(byte[] bytes) {
mBytes = bytes;
- mOffset = offset;
- mLength = length;
}
}
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 788711d..e8b2045 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -729,12 +729,22 @@
start - widthStart, end - start);
}
- // If ellipsize is in marquee mode, do not apply ellipsis on the first line
- if (ellipsize != null && (ellipsize != TextUtils.TruncateAt.MARQUEE || j != 0)) {
+ if (ellipsize != null) {
+ // If there is only one line, then do any type of ellipsis except when it is MARQUEE
+ // if there are multiple lines, just allow END ellipsis on the last line
+ boolean firstLine = (j == 0);
+ boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
- calculateEllipsis(start, end, widths, widthStart,
- ellipsisWidth, ellipsize, j,
- textWidth, paint, forceEllipsis);
+
+ boolean doEllipsis = (firstLine && !moreChars &&
+ ellipsize != TextUtils.TruncateAt.MARQUEE) ||
+ (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
+ ellipsize == TextUtils.TruncateAt.END);
+ if (doEllipsis) {
+ calculateEllipsis(start, end, widths, widthStart,
+ ellipsisWidth, ellipsize, j,
+ textWidth, paint, forceEllipsis);
+ }
}
mLineCount++;
@@ -797,8 +807,8 @@
ellipsisStart = i;
ellipsisCount = len - i;
- if (forceEllipsis && ellipsisCount == 0 && i > 0) {
- ellipsisStart = i - 1;
+ if (forceEllipsis && ellipsisCount == 0 && len > 0) {
+ ellipsisStart = len - 1;
ellipsisCount = 1;
}
} else {
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ef3d3fa5..ae81537 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -36,10 +36,14 @@
public static final int ROTATION_270 = 3;
/**
- * Create Surface from a SurfaceTexture.
+ * Create Surface from a {@link SurfaceTexture}.
*
- * @param surfaceTexture The {@link SurfaceTexture} that is updated by this Surface.
- * @hide
+ * Images drawn to the Surface will be made available to the {@link
+ * SurfaceTexture}, which can attach them an OpenGL ES texture via {@link
+ * SurfaceTexture#updateTexImage}.
+ *
+ * @param surfaceTexture The {@link SurfaceTexture} that is updated by this
+ * Surface.
*/
public Surface(SurfaceTexture surfaceTexture) {
if (DEBUG_RELEASE) {
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
index ef1906c..1b9a25e 100644
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ b/core/java/android/webkit/HTML5VideoInline.java
@@ -5,6 +5,7 @@
import android.media.MediaPlayer;
import android.webkit.HTML5VideoView;
import android.webkit.HTML5VideoViewProxy;
+import android.view.Surface;
import android.opengl.GLES20;
/**
@@ -38,7 +39,10 @@
@Override
public void decideDisplayMode() {
- mPlayer.setTexture(getSurfaceTexture(getVideoLayerId()));
+ SurfaceTexture surfaceTexture = getSurfaceTexture(getVideoLayerId());
+ Surface surface = new Surface(surfaceTexture);
+ mPlayer.setSurface(surface);
+ surface.release();
}
// Normally called immediately after setVideoURI. But for full screen,
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index ff13dcb..b89b8ec 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -112,6 +112,8 @@
private void scheduleSpellCheck() {
if (mLength == 0) return;
+ if (spellCheckerSession == null) return;
+
if (mChecker != null) {
mTextView.removeCallbacks(mChecker);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index e9662ae..94f1604 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8928,6 +8928,7 @@
stopSelectionActionMode();
} else {
selectCurrentWord();
+ getSelectionController().show();
}
handled = true;
}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 4cf4afa..494a2b3 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -119,6 +119,11 @@
} gBinderProxyOffsets;
+static struct class_offsets_t
+{
+ jmethodID mGetName;
+} gClassOffsets;
+
// ----------------------------------------------------------------------------
static struct parcel_offsets_t
@@ -452,22 +457,18 @@
// Okay, something is wrong -- we have a hard reference to a live death
// recipient on the VM side, but the list is being torn down.
JNIEnv* env = javavm_to_jnienv(mVM);
- ScopedLocalRef<jclass> classRef(env, env->GetObjectClass(mObject));
- jmethodID getnameMethod = env->GetMethodID(classRef.get(),
- "getName", "()Ljava/lang/String;");
- if (getnameMethod) {
- ScopedLocalRef<jstring> nameRef(env,
- (jstring) env->CallObjectMethod(classRef.get(), getnameMethod));
- ScopedUtfChars nameUtf(env, nameRef.get());
- if (nameUtf.c_str() != NULL) {
- LOGW("BinderProxy is being destroyed but the application did not call "
- "unlinkToDeath to unlink all of its death recipients beforehand. "
- "Releasing leaked death recipient: %s", nameUtf.c_str());
- } else {
- LOGW("BinderProxy being destroyed; unable to get DR object name");
- env->ExceptionClear();
- }
- } else LOGW("BinderProxy being destroyed; unable to find DR class getName");
+ ScopedLocalRef<jclass> objClassRef(env, env->GetObjectClass(mObject));
+ ScopedLocalRef<jstring> nameRef(env,
+ (jstring) env->CallObjectMethod(objClassRef.get(), gClassOffsets.mGetName));
+ ScopedUtfChars nameUtf(env, nameRef.get());
+ if (nameUtf.c_str() != NULL) {
+ LOGW("BinderProxy is being destroyed but the application did not call "
+ "unlinkToDeath to unlink all of its death recipients beforehand. "
+ "Releasing leaked death recipient: %s", nameUtf.c_str());
+ } else {
+ LOGW("BinderProxy being destroyed; unable to get DR object name");
+ env->ExceptionClear();
+ }
}
}
@@ -1230,6 +1231,11 @@
= env->GetFieldID(clazz, "mOrgue", "I");
assert(gBinderProxyOffsets.mOrgue);
+ clazz = env->FindClass("java/lang/Class");
+ LOG_FATAL_IF(clazz == NULL, "Unable to find java.lang.Class");
+ gClassOffsets.mGetName = env->GetMethodID(clazz, "getName", "()Ljava/lang/String;");
+ assert(gClassOffsets.mGetName);
+
return AndroidRuntime::registerNativeMethods(
env, kBinderProxyPathName,
gBinderProxyMethods, NELEM(gBinderProxyMethods));
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_status_land.xml b/core/res/res/layout-sw600dp/keyguard_screen_status_land.xml
index f3850d5..3a7c1e1 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_status_land.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_status_land.xml
@@ -42,11 +42,8 @@
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_alignParentLeft="true"
android:layout_marginTop="8dip"
android:layout_marginBottom="8dip"
- android:layout_marginLeft="-10dip"
>
<!-- Because we can't have multi-tone fonts, we render two TextViews, one on
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_status_port.xml b/core/res/res/layout-sw600dp/keyguard_screen_status_port.xml
index f4c99f1..c02341e 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_status_port.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_status_port.xml
@@ -45,8 +45,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dip"
- android:layout_marginBottom="8dip"
- android_layout_marginLeft="-10dip">
+ android:layout_marginBottom="8dip">
<!-- Because we can't have multi-tone fonts, we render two TextViews, one on
top of the other. Hence the redundant layout... -->
diff --git a/core/res/res/values-sw600dp/colors.xml b/core/res/res/values-sw600dp/colors.xml
index 6b5a55a..edd2712 100644
--- a/core/res/res/values-sw600dp/colors.xml
+++ b/core/res/res/values-sw600dp/colors.xml
@@ -21,7 +21,7 @@
<!-- keyguard clock -->
<color name="lockscreen_clock_background">#b3ffffff</color>
<color name="lockscreen_clock_foreground">#7affffff</color>
- <color name="lockscreen_clock_am_pm">#ff9a9a9a</color>
+ <color name="lockscreen_clock_am_pm">#ffffffff</color>
<color name="lockscreen_owner_info">#ff9a9a9a</color>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index de10825..547e1fc 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1763,7 +1763,7 @@
--> <skip />
<!-- On the keyguard screen, it shows the carrier the phone is connected to. This is displayed if the phone is not connected to a carrier.-->
- <string name="lockscreen_carrier_default">(No service)</string>
+ <string name="lockscreen_carrier_default">No service.</string>
<!-- Shown in the lock screen to tell the user that the screen is locked. -->
<string name="lockscreen_screen_locked">Screen locked.</string>
diff --git a/docs/html/sdk/ndk/index.jd b/docs/html/sdk/ndk/index.jd
index 97df84f..f87e1f6 100644
--- a/docs/html/sdk/ndk/index.jd
+++ b/docs/html/sdk/ndk/index.jd
@@ -1,16 +1,16 @@
ndk=true
-ndk.win_download=android-ndk-r6-windows.zip
-ndk.win_bytes=67642809
-ndk.win_checksum=9c7d5ccc02151a3e5e950c70dc05ac6d
+ndk.win_download=android-ndk-r6b-windows.zip
+ndk.win_bytes=67670219
+ndk.win_checksum=f496b48fffb6d341303de170a081b812
-ndk.mac_download=android-ndk-r6-darwin-x86.tar.bz2
-ndk.mac_bytes=52682746
-ndk.mac_checksum=a154905e49a6246abd823b75b6eda738
+ndk.mac_download=android-ndk-r6b-darwin-x86.tar.bz2
+ndk.mac_bytes=52798843
+ndk.mac_checksum=65f2589ac1b08aabe3183f9ed1a8ce8e
-ndk.linux_download=android-ndk-r6-linux-x86.tar.bz2
-ndk.linux_bytes=46425290
-ndk.linux_checksum=ff0a43085fe206696d5cdcef3f4f4637
+ndk.linux_download=android-ndk-r6b-linux-x86.tar.bz2
+ndk.linux_bytes=46532436
+ndk.linux_checksum=309f35e49b64313cfb20ac428df4cec2
page.title=Android NDK
@jd:body
@@ -58,10 +58,42 @@
}
</style>
-
<div class="toggleable open">
<a href="#" onclick="return toggleDiv(this)"><img src=
"{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px">
+ Android NDK, Revision 6b</a> <em>(August 2011)</em>
+
+ <div class="toggleme">
+ <p>This release of the NDK does not include any new features compared to r6. The r6b release
+ addresses the following issues in the r6 release:</p>
+ <dl>
+ <dt>Important bug fixes</dt>
+ <dd>
+ <ul>
+ <li>Fixed the build when <code>APP_ABI="armeabi x86"</code> is used for
+ multi-architecture builds.</li>
+ <li>Fixed the location of prebuilt STLport binaries in the NDK release package.
+ A bug in the packaging script placed them in the wrong location.</li>
+ <li>Fixed <code>atexit()</code> usage in shared libraries with the x86standalone
+ toolchain.</li>
+ <li>Fixed <code>make-standalone-toolchain.sh --arch=x86</code>. It used to fail
+ to copy the proper GNU libstdc++ binaries to the right location.</li>
+ <li>Fixed the standalone toolchain linker warnings about missing the definition and
+ size for the <code>__dso_handle</code> symbol (ARM only).</li>
+ <li>Fixed the inclusion order of <code>$(SYSROOT)/usr/include</code> for x86 builds.
+ See the <a href="http://code.google.com/p/android/issues/detail?id=18540">bug</a> for
+ more information.</li>
+ <li>Fixed the definitions of <code>ptrdiff_t</code> and <code>size_t</code> in
+ x86-specific systems when they are used with the x86 standalone toolchain.</li>
+ </ul>
+ </dd>
+ </dl>
+ </div>
+</div>
+
+<div class="toggleable closed">
+ <a href="#" onclick="return toggleDiv(this)"><img src=
+ "{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px">
Android NDK, Revision 6</a> <em>(July 2011)</em>
<div class="toggleme">
diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs
index 1b1fc8d..a00ca12 100644
--- a/docs/html/sdk/sdk_toc.cs
+++ b/docs/html/sdk/sdk_toc.cs
@@ -183,7 +183,7 @@
<span style="display:none" class="zh-TW"></span>
</h2>
<ul>
- <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r6</a>
+ <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r6b</a>
<span class="new">new!</span>
</li>
<li><a href="<?cs var:toroot ?>sdk/ndk/overview.html">What is the NDK?</a></li>
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index e0d7898..6a15f6e 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -185,6 +185,10 @@
static status_t unregisterEffect(int id);
static status_t setEffectEnabled(int id, bool enabled);
+ // clear stream to output mapping cache (gStreamOutputMap)
+ // and output configuration cache (gOutputs)
+ static void clearAudioConfigCache();
+
static const sp<IAudioPolicyService>& get_audio_policy_service();
// ----------------------------------------------------------------------------
@@ -236,7 +240,8 @@
// mapping between stream types and outputs
static DefaultKeyedVector<int, audio_io_handle_t> gStreamOutputMap;
- // list of output descritor containing cached parameters (sampling rate, framecount, channel count...)
+ // list of output descriptors containing cached parameters
+ // (sampling rate, framecount, channel count...)
static DefaultKeyedVector<audio_io_handle_t, OutputDescriptor *> gOutputs;
};
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 9acf99b..dd05e61 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -394,14 +394,14 @@
bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
// If the glyph is too tall, don't cache it
- if (glyph.fHeight > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
+ if (glyph.fHeight + 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
if (mCacheHeight < MAX_TEXT_CACHE_HEIGHT) {
// Default cache not large enough for large glyphs - resize cache to
// max size and try again
flushAllAndInvalidate();
initTextTexture(true);
}
- if (glyph.fHeight > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
+ if (glyph.fHeight + 2 > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
LOGE("Font size to large to fit in cache. width, height = %i, %i",
(int) glyph.fWidth, (int) glyph.fHeight);
return false;
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index dbe4115..bfc6b5d 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -2637,7 +2637,7 @@
notifyTopOfAudioFocusStack();
// there's a new top of the stack, let the remote control know
synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
}
}
} else {
@@ -2680,7 +2680,7 @@
notifyTopOfAudioFocusStack();
// there's a new top of the stack, let the remote control know
synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
}
}
}
@@ -2784,7 +2784,7 @@
// there's a new top of the stack, let the remote control know
synchronized(mRCStack) {
- checkUpdateRemoteControlDisplay(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
}
}//synchronized(mAudioFocusLock)
@@ -3182,7 +3182,7 @@
* Helper function:
* Called synchronized on mRCStack
*/
- private void clearRemoteControlDisplay() {
+ private void clearRemoteControlDisplay_syncRcs() {
synchronized(mCurrentRcLock) {
mCurrentRcClient = null;
}
@@ -3195,14 +3195,14 @@
* Called synchronized on mRCStack
* mRCStack.empty() is false
*/
- private void updateRemoteControlDisplay(int infoChangedFlags) {
+ private void updateRemoteControlDisplay_syncRcs(int infoChangedFlags) {
RemoteControlStackEntry rcse = mRCStack.peek();
int infoFlagsAboutToBeUsed = infoChangedFlags;
// this is where we enforce opt-in for information display on the remote controls
// with the new AudioManager.registerRemoteControlClient() API
if (rcse.mRcClient == null) {
//Log.w(TAG, "Can't update remote control display with null remote control client");
- clearRemoteControlDisplay();
+ clearRemoteControlDisplay_syncRcs();
return;
}
synchronized(mCurrentRcLock) {
@@ -3225,11 +3225,11 @@
* that has changed, if applicable (checking for the update conditions might trigger a
* clear, rather than an update event).
*/
- private void checkUpdateRemoteControlDisplay(int infoChangedFlags) {
+ private void checkUpdateRemoteControlDisplay_syncRcs(int infoChangedFlags) {
// determine whether the remote control display should be refreshed
// if either stack is empty, there is a mismatch, so clear the RC display
if (mRCStack.isEmpty() || mFocusStack.isEmpty()) {
- clearRemoteControlDisplay();
+ clearRemoteControlDisplay_syncRcs();
return;
}
// if the top of the two stacks belong to different packages, there is a mismatch, clear
@@ -3237,17 +3237,17 @@
&& (mFocusStack.peek().mPackageName != null)
&& !(mRCStack.peek().mCallingPackageName.compareTo(
mFocusStack.peek().mPackageName) == 0)) {
- clearRemoteControlDisplay();
+ clearRemoteControlDisplay_syncRcs();
return;
}
// if the audio focus didn't originate from the same Uid as the one in which the remote
// control information will be retrieved, clear
if (mRCStack.peek().mCallingUid != mFocusStack.peek().mCallingUid) {
- clearRemoteControlDisplay();
+ clearRemoteControlDisplay_syncRcs();
return;
}
// refresh conditions were verified: update the remote controls
- updateRemoteControlDisplay(infoChangedFlags);
+ updateRemoteControlDisplay_syncRcs(infoChangedFlags);
}
/** see AudioManager.registerMediaButtonEventReceiver(ComponentName eventReceiver) */
@@ -3258,7 +3258,7 @@
synchronized(mRCStack) {
pushMediaButtonReceiver(eventReceiver);
// new RC client, assume every type of information shall be queried
- checkUpdateRemoteControlDisplay(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
}
}
}
@@ -3273,7 +3273,7 @@
removeMediaButtonReceiver(eventReceiver);
if (topOfStackWillChange) {
// current RC client will change, assume every type of info needs to be queried
- checkUpdateRemoteControlDisplay(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
}
}
}
@@ -3329,7 +3329,7 @@
// if the eventReceiver is at the top of the stack
// then check for potential refresh of the remote controls
if (isCurrentRcController(eventReceiver)) {
- checkUpdateRemoteControlDisplay(RC_INFO_ALL);
+ checkUpdateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
}
}
}
@@ -3426,7 +3426,9 @@
}
/**
- * Register an IRemoteControlDisplay and notify all IRemoteControlClient of the new display.
+ * Register an IRemoteControlDisplay.
+ * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
+ * at the top of the stack to update the new display with its information.
* Since only one IRemoteControlDisplay is supported, this will unregister the previous display.
* @param rcd the IRemoteControlDisplay to register. No effect if null.
*/
@@ -3458,20 +3460,8 @@
}
}
- // we have a new display, tell the current client that it needs to send info
- // (following lock order: mRCStack then mCurrentRcLock)
- synchronized(mCurrentRcLock) {
- if (mCurrentRcClient != null) {
- // tell the current client that it needs to send info
- try {
- mCurrentRcClient.onInformationRequested(mCurrentRcClientGen,
- RC_INFO_ALL, mArtworkExpectedWidth, mArtworkExpectedHeight);
- } catch (RemoteException e) {
- Log.e(TAG, "Current valid remote client is dead: "+e);
- mCurrentRcClient = null;
- }
- }
- }
+ // we have a new display, of which all the clients are now aware: have it be updated
+ updateRemoteControlDisplay_syncRcs(RC_INFO_ALL);
}
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 66bd56a..1ee9a1f 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -381,7 +381,7 @@
* <td>{} </p></td>
* <td>This method can be called in any state and calling it does not change
* the object state. </p></td></tr>
- * <tr><td>setTexture </p></td>
+ * <tr><td>setSurface </p></td>
* <td>any </p></td>
* <td>{} </p></td>
* <td>This method can be called in any state and calling it does not change
@@ -608,7 +608,7 @@
* portion of the media.
*
* Either a surface holder or surface must be set if a display or video sink
- * is needed. Not calling this method or {@link #setTexture(SurfaceTexture)}
+ * is needed. Not calling this method or {@link #setSurface(Surface)}
* when playing back a video will result in only the audio track being played.
* A null surface holder or surface will result in only the audio track being
* played.
@@ -629,14 +629,21 @@
/**
* Sets the {@link Surface} to be used as the sink for the video portion of
- * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but does not
- * support {@link #setScreenOnWhilePlaying(boolean)} or {@link #updateSurfaceScreenOn()}.
- * Setting a Surface will un-set any Surface or SurfaceHolder that was previously set.
+ * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but
+ * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a
+ * Surface will un-set any Surface or SurfaceHolder that was previously set.
* A null surface will result in only the audio track being played.
*
- * @param surface The {@link Surface} to be used for the video portion of the media.
+ * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
+ * returned from {@link SurfaceTexture#getTimestamp()} will have an
+ * unspecified zero point. These timestamps cannot be directly compared
+ * between different media sources, different instances of the same media
+ * source, or multiple runs of the same program. The timestamp is normally
+ * monotonically increasing and is unaffected by time-of-day adjustments,
+ * but it is reset when the position is set.
*
- * @hide Pending review by API council.
+ * @param surface The {@link Surface} to be used for the video portion of
+ * the media.
*/
public void setSurface(Surface surface) {
if (mScreenOnWhilePlaying && surface != null) {
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index bb91fa9..853a5f6 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -727,6 +727,14 @@
}
+void AudioSystem::clearAudioConfigCache()
+{
+ Mutex::Autolock _l(gLock);
+ LOGV("clearAudioConfigCache()");
+ gStreamOutputMap.clear();
+ gOutputs.clear();
+}
+
// ---------------------------------------------------------------------------
void AudioSystem::AudioPolicyServiceClient::binderDied(const wp<IBinder>& who) {
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index cecedb5..3b6c64d 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -1164,6 +1164,10 @@
cblk->cv.broadcast();
cblk->lock.unlock();
+ // refresh the audio configuration cache in this process to make sure we get new
+ // output parameters in getOutput_l() and createTrack_l()
+ AudioSystem::clearAudioConfigCache();
+
// if the new IAudioTrack is created, createTrack_l() will modify the
// following member variables: mAudioTrack, mCblkMemory and mCblk.
// It will also delete the strong references on previous IAudioTrack and IMemory
diff --git a/media/tests/MediaDump/src/com/android/mediadump/VideoDumpView.java b/media/tests/MediaDump/src/com/android/mediadump/VideoDumpView.java
index 809ee82..f76cf37 100644
--- a/media/tests/MediaDump/src/com/android/mediadump/VideoDumpView.java
+++ b/media/tests/MediaDump/src/com/android/mediadump/VideoDumpView.java
@@ -49,6 +49,7 @@
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View;
import android.widget.MediaController;
@@ -569,7 +570,9 @@
mSurface = new SurfaceTexture(mTextureID);
mSurface.setOnFrameAvailableListener(this);
- mMediaPlayer.setTexture(mSurface);
+ Surface surface = new Surface(mSurface);
+ mMediaPlayer.setSurface(surface);
+ surface.release();
try {
mMediaPlayer.prepare();
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index d617af8..94efa74 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1362,6 +1362,7 @@
for (int stream = 0; stream < AUDIO_STREAM_CNT; stream++) {
mStreamTypes[stream].volume = mAudioFlinger->streamVolumeInternal(stream);
mStreamTypes[stream].mute = mAudioFlinger->streamMute(stream);
+ mStreamTypes[stream].valid = true;
}
}
@@ -1530,6 +1531,14 @@
chain->setStrategy(AudioSystem::getStrategyForStream((audio_stream_type_t)track->type()));
chain->incTrackCnt();
}
+
+ // invalidate track immediately if the stream type was moved to another thread since
+ // createTrack() was called by the client process.
+ if (!mStreamTypes[streamType].valid) {
+ LOGW("createTrack_l() on thread %p: invalidating track on stream %d",
+ this, streamType);
+ android_atomic_or(CBLK_INVALID_ON, &track->mCblk->flags);
+ }
}
lStatus = NO_ERROR;
@@ -2219,6 +2228,14 @@
}
}
+void AudioFlinger::PlaybackThread::setStreamValid(int streamType, bool valid)
+{
+ LOGV ("PlaybackThread::setStreamValid() thread %p, streamType %d, valid %d",
+ this, streamType, valid);
+ Mutex::Autolock _l(mLock);
+
+ mStreamTypes[streamType].valid = valid;
+}
// getTrackName_l() must be called with ThreadBase::mLock held
int AudioFlinger::MixerThread::getTrackName_l()
@@ -5074,11 +5091,14 @@
LOGV("setStreamOutput() stream %d to output %d", stream, output);
audioConfigChanged_l(AudioSystem::STREAM_CONFIG_CHANGED, output, &stream);
+ dstThread->setStreamValid(stream, true);
+
for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
PlaybackThread *thread = mPlaybackThreads.valueAt(i).get();
if (thread != dstThread &&
thread->type() != ThreadBase::DIRECT) {
MixerThread *srcThread = (MixerThread *)thread;
+ srcThread->setStreamValid(stream, false);
srcThread->invalidateTracks(stream);
}
}
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 1ceb0ec..2e05593 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -751,14 +751,18 @@
virtual uint32_t hasAudioSession(int sessionId);
virtual uint32_t getStrategyForSession_l(int sessionId);
+ void setStreamValid(int streamType, bool valid);
+
struct stream_type_t {
stream_type_t()
: volume(1.0f),
- mute(false)
+ mute(false),
+ valid(true)
{
}
float volume;
bool mute;
+ bool valid;
};
protected:
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index e17d98d..6743da0 100755
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -36,6 +36,7 @@
import android.content.res.Resources;
+import java.util.TimeZone;
/**
@@ -231,7 +232,7 @@
public static class TimeStamp extends Time {
public TimeStamp() {
- super(Time.TIMEZONE_UTC);
+ super(TimeZone.getDefault().getID()); // 3GPP2 timestamps use the local timezone
}
public static TimeStamp fromByteArray(byte[] data) {
diff --git a/tests/BandwidthTests/Android.mk b/tests/BandwidthTests/Android.mk
new file mode 100644
index 0000000..2cc2009
--- /dev/null
+++ b/tests/BandwidthTests/Android.mk
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2011 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.
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := BandwidthEnforcementTest
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/BandwidthTests/AndroidManifest.xml b/tests/BandwidthTests/AndroidManifest.xml
new file mode 100644
index 0000000..19f38ca
--- /dev/null
+++ b/tests/BandwidthTests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.bandwidthenforcement">
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <application>
+ <activity android:name=".BandwidthEnforcementTestActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <!-- adb shell am startservice -n com.android.tests.bandwidthenforcement/.BandwidthEnforcementTestService -->
+ <service android:name=".BandwidthEnforcementTestService" android:exported="true" />
+ </application>
+</manifest>
diff --git a/tests/BandwidthTests/res/layout/main.xml b/tests/BandwidthTests/res/layout/main.xml
new file mode 100644
index 0000000..3392b21
--- /dev/null
+++ b/tests/BandwidthTests/res/layout/main.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_name" />
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/app_desc" />
+</LinearLayout>
diff --git a/tests/BandwidthTests/res/values/strings.xml b/tests/BandwidthTests/res/values/strings.xml
new file mode 100644
index 0000000..a4a78c2
--- /dev/null
+++ b/tests/BandwidthTests/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">BandwidthEnforcementTest</string>
+ <string name="app_desc">Tries several tricks to get Internet access.</string>
+ <string name="start">Start</string>
+ <string name="stop">Stop</string>
+</resources>
diff --git a/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestActivity.java b/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestActivity.java
new file mode 100644
index 0000000..f0e43ac
--- /dev/null
+++ b/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tests.bandwidthenforcement;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class BandwidthEnforcementTestActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}
diff --git a/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java b/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java
new file mode 100644
index 0000000..a2427f5
--- /dev/null
+++ b/tests/BandwidthTests/src/com/android/tests/bandwidthenforcement/BandwidthEnforcementTestService.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tests.bandwidthenforcement;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.net.SntpClient;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.URL;
+import java.util.Random;
+
+import libcore.io.Streams;
+
+/*
+ * Test Service that tries to connect to the web via different methods and outputs the results to
+ * the log and a output file.
+ */
+public class BandwidthEnforcementTestService extends IntentService {
+ private static final String TAG = "BandwidthEnforcementTestService";
+ private static final String OUTPUT_FILE = "BandwidthEnforcementTestServiceOutputFile";
+
+ public BandwidthEnforcementTestService() {
+ super(TAG);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ Log.d(TAG, "Trying to establish a connection.");
+ // Read output file path from intent.
+ String outputFile = intent.getStringExtra(OUTPUT_FILE);
+ dumpResult("testUrlConnection", testUrlConnection(), outputFile);
+ dumpResult("testUrlConnectionv6", testUrlConnectionv6(), outputFile);
+ dumpResult("testSntp", testSntp(), outputFile);
+ dumpResult("testDns", testDns(), outputFile);
+ }
+
+ public static void dumpResult(String tag, boolean result, String outputFile) {
+ Log.d(TAG, "Test output file: " + outputFile);
+ try {
+ if (outputFile != null){
+ File extStorage = Environment.getExternalStorageDirectory();
+ File outFile = new File(extStorage, outputFile);
+ FileWriter writer = new FileWriter(outFile, true);
+ BufferedWriter out = new BufferedWriter(writer);
+ if (result) {
+ out.append(tag + ":fail\n");
+ } else {
+ out.append(tag + ":pass\n");
+ }
+ out.close();
+ }
+ if (result) {
+ Log.e(TAG, tag + " FAILURE ====================");
+ Log.e(TAG, tag + " FAILURE was able to use data");
+ Log.e(TAG, tag + " FAILURE ====================");
+ } else {
+ Log.d(TAG, tag + " success; unable to use data");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Could not write file " + e.getMessage());
+ }
+ }
+
+ /**
+ * Tests a normal http url connection.
+ * @return true if it was able to connect, false otherwise.
+ */
+ public static boolean testUrlConnection() {
+ try {
+ final HttpURLConnection conn = (HttpURLConnection) new URL("http://www.google.com/")
+ .openConnection();
+ try {
+ conn.connect();
+ final String content = Streams.readFully(
+ new InputStreamReader(conn.getInputStream()));
+ if (content.contains("Google")) {
+ return true;
+ }
+ } finally {
+ conn.disconnect();
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "error: " + e);
+ }
+ return false;
+ }
+
+ /**
+ * Tests a ipv6 http url connection.
+ * @return true if it was able to connect, false otherwise.
+ */
+ public static boolean testUrlConnectionv6() {
+ try {
+ final HttpURLConnection conn = (HttpURLConnection) new URL("http://ipv6.google.com/")
+ .openConnection();
+ try {
+ conn.connect();
+ final String content = Streams.readFully(
+ new InputStreamReader(conn.getInputStream()));
+ if (content.contains("Google")) {
+ return true;
+ }
+ } finally {
+ conn.disconnect();
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "error: " + e);
+ }
+ return false;
+ }
+
+ /**
+ * Tests to connect via sntp.
+ * @return true if it was able to connect, false otherwise.
+ */
+ public static boolean testSntp() {
+ final SntpClient client = new SntpClient();
+ if (client.requestTime("0.pool.ntp.org", 10000)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Tests dns query.
+ * @return true if it was able to connect, false otherwise.
+ */
+ public static boolean testDns() {
+ try {
+ final DatagramSocket socket = new DatagramSocket();
+ try {
+ socket.setSoTimeout(10000);
+
+ final byte[] query = buildDnsQuery("www", "android", "com");
+ final DatagramPacket queryPacket = new DatagramPacket(
+ query, query.length, InetAddress.parseNumericAddress("8.8.8.8"), 53);
+ socket.send(queryPacket);
+
+ final byte[] reply = new byte[query.length];
+ final DatagramPacket replyPacket = new DatagramPacket(reply, reply.length);
+ socket.receive(replyPacket);
+ return true;
+
+ } finally {
+ socket.close();
+ }
+ } catch (IOException e) {
+ Log.d(TAG, "error: " + e);
+ }
+ return false;
+ }
+
+ /**
+ * Helper method to build a dns query
+ * @param query the dns strings of the server
+ * @return the byte array of the dns query to send
+ * @throws IOException
+ */
+ private static byte[] buildDnsQuery(String... query) throws IOException {
+ final Random random = new Random();
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ final byte[] id = new byte[2];
+ random.nextBytes(id);
+
+ out.write(id);
+ out.write(new byte[] { 0x01, 0x00 });
+ out.write(new byte[] { 0x00, 0x01 });
+ out.write(new byte[] { 0x00, 0x00 });
+ out.write(new byte[] { 0x00, 0x00 });
+ out.write(new byte[] { 0x00, 0x00 });
+
+ for (String phrase : query) {
+ final byte[] bytes = phrase.getBytes("US-ASCII");
+ out.write(bytes.length);
+ out.write(bytes);
+ }
+ out.write(0x00);
+
+ out.write(new byte[] { 0x00, 0x01 });
+ out.write(new byte[] { 0x00, 0x01 });
+
+ return out.toByteArray();
+ }
+}
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 3b3cbf3..49effa8 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -72,6 +72,7 @@
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
+import javax.sip.header.ContactHeader;
import javax.sip.header.ExpiresHeader;
import javax.sip.header.FromHeader;
import javax.sip.header.HeaderAddress;
@@ -873,16 +874,21 @@
}
private int getExpiryTime(Response response) {
- int expires = EXPIRY_TIME;
- ExpiresHeader expiresHeader = (ExpiresHeader)
- response.getHeader(ExpiresHeader.NAME);
- if (expiresHeader != null) expires = expiresHeader.getExpires();
- expiresHeader = (ExpiresHeader)
- response.getHeader(MinExpiresHeader.NAME);
- if (expiresHeader != null) {
- expires = Math.max(expires, expiresHeader.getExpires());
+ int time = -1;
+ ContactHeader contact = (ContactHeader) response.getHeader(ContactHeader.NAME);
+ if (contact != null) {
+ time = contact.getExpires();
}
- return expires;
+ ExpiresHeader expires = (ExpiresHeader) response.getHeader(ExpiresHeader.NAME);
+ if (expires != null && (time < 0 || time > expires.getExpires())) {
+ time = expires.getExpires();
+ }
+ expires = (ExpiresHeader) response.getHeader(MinExpiresHeader.NAME);
+ if (expires != null && time < expires.getExpires()) {
+ time = expires.getExpires();
+ }
+ Log.v(TAG, "Expiry time = " + time);
+ return (time > 0) ? time : EXPIRY_TIME;
}
private boolean registeringToReady(EventObject evt)