diff options
34 files changed, 734 insertions, 148 deletions
diff --git a/api/current.txt b/api/current.txt index dfc17ba7bde8..6daf77ebb735 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10681,6 +10681,7 @@ package android.media { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.provider { 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 @@ package android.view { } 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 5321c6aa830a..1f2b3429f3f8 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -125,19 +125,6 @@ public final class ContactsContract { 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 @@ public final class ContactsContract { } /** - * 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 @@ public final class ContactsContract { } } + + /** + * 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 @@ public final class ContactsContract { * 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 final class ContactsContract { */ 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 @@ public final class ContactsContract { * 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 final class ContactsContract { 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 89b6f321e756..f8d014232ba8 100644 --- a/core/java/android/speech/tts/AudioPlaybackHandler.java +++ b/core/java/android/speech/tts/AudioPlaybackHandler.java @@ -390,10 +390,10 @@ class AudioPlaybackHandler { 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 @@ class AudioPlaybackHandler { } 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 @@ class AudioPlaybackHandler { 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 7dbf1acdaf1f..0cca06accd1f 100644 --- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java +++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java @@ -85,6 +85,7 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { // 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 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { // 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 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { + 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 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { 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 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { 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 7da5daabdb6d..3e905d6cc5dd 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 @@ import java.util.LinkedList; * 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 class SynthesisMessageParams extends MessageParams { 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 @@ final class SynthesisMessageParams extends MessageParams { mPan = pan; mLogger = logger; + mBytesPerFrame = getBytesPerFrame(mAudioFormat) * mChannelCount; + // initially null. mAudioTrack = null; mBytesWritten = 0; @@ -64,18 +75,36 @@ final class SynthesisMessageParams extends MessageParams { 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 @@ final class SynthesisMessageParams extends MessageParams { 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 788711d9ea88..e8b2045ef828 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -729,12 +729,22 @@ public class StaticLayout extends Layout { 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 @@ public class StaticLayout extends Layout { 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 ef3d3fa59a93..ae815373371e 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -36,10 +36,14 @@ public class Surface implements Parcelable { 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 ef1906c1ea2c..1b9a25ec5982 100644 --- a/core/java/android/webkit/HTML5VideoInline.java +++ b/core/java/android/webkit/HTML5VideoInline.java @@ -5,6 +5,7 @@ import android.graphics.SurfaceTexture; import android.media.MediaPlayer; import android.webkit.HTML5VideoView; import android.webkit.HTML5VideoViewProxy; +import android.view.Surface; import android.opengl.GLES20; /** @@ -38,7 +39,10 @@ public class HTML5VideoInline extends HTML5VideoView{ @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 ff13dcbf54f4..b89b8ec442c3 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -112,6 +112,8 @@ public class SpellChecker implements SpellCheckerSessionListener { 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 e9662aeb760e..94f1604b16e1 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8928,6 +8928,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener stopSelectionActionMode(); } else { selectCurrentWord(); + getSelectionController().show(); } handled = true; } diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 4cf4afabec14..494a2b33ade2 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -119,6 +119,11 @@ static struct binderproxy_offsets_t } gBinderProxyOffsets; +static struct class_offsets_t +{ + jmethodID mGetName; +} gClassOffsets; + // ---------------------------------------------------------------------------- static struct parcel_offsets_t @@ -452,22 +457,18 @@ public: // 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 @@ static int int_register_android_os_BinderProxy(JNIEnv* env) = 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 f3850d5d02d4..3a7c1e1c4bf9 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 f4c99f106403..c02341e4bc23 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 6b5a55a3a0d5..edd2712ecfb0 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 de10825af5c9..547e1fcb8663 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 97df84f1df49..f87e1f6be976 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 @@ padding: .25em 1em; } </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 1b1fc8d3dbf0..a00ca125a6cf 100644 --- a/docs/html/sdk/sdk_toc.cs +++ b/docs/html/sdk/sdk_toc.cs @@ -183,7 +183,7 @@ r3</a> <span class="new">new!</span></li> <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 e0d78980a8ce..6a15f6ec90a6 100644 --- a/include/media/AudioSystem.h +++ b/include/media/AudioSystem.h @@ -185,6 +185,10 @@ public: 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 @@ private: // 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 9acf99be1a15..dd05e61b41e9 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -394,14 +394,14 @@ void FontRenderer::flushAllAndInvalidate() { 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 dbe4115e0c90..bfc6b5d81c59 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -2637,7 +2637,7 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { // 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 @@ public class AudioService extends IAudioService.Stub { * Helper function: * Called synchronized on mRCStack */ - private void clearRemoteControlDisplay() { + private void clearRemoteControlDisplay_syncRcs() { synchronized(mCurrentRcLock) { mCurrentRcClient = null; } @@ -3195,14 +3195,14 @@ public class AudioService extends IAudioService.Stub { * 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 @@ public class AudioService extends IAudioService.Stub { * 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 @@ public class AudioService extends IAudioService.Stub { && (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 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { 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 @@ public class AudioService extends IAudioService.Stub { // 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 @@ public class AudioService extends IAudioService.Stub { } /** - * 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 @@ public class AudioService extends IAudioService.Stub { } } - // 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 66bd56a01b3b..1ee9a1fcc52b 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -381,7 +381,7 @@ import java.lang.ref.WeakReference; * <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 @@ public class MediaPlayer * 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 @@ public class MediaPlayer /** * 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 bb91fa96b4d7..853a5f6b4b51 100644 --- a/media/libmedia/AudioSystem.cpp +++ b/media/libmedia/AudioSystem.cpp @@ -727,6 +727,14 @@ status_t AudioSystem::isStreamActive(int stream, bool* state, uint32_t inPastMs) } +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 cecedb517d64..3b6c64d5fb51 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -1164,6 +1164,10 @@ status_t AudioTrack::restoreTrack_l(audio_track_cblk_t*& cblk, bool fromStart) 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 809ee8208dfc..f76cf37b9763 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.opengl.Matrix; 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 @@ class VideoDumpView extends GLSurfaceView implements MediaPlayerControl { 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 d617af8b3d2a..94efa749e08f 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -1362,6 +1362,7 @@ AudioFlinger::PlaybackThread::PlaybackThread(const sp<AudioFlinger>& audioFlinge 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 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra 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::MixerThread::invalidateTracks(int streamType) } } +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 @@ status_t AudioFlinger::setStreamOutput(uint32_t stream, int output) 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 1ceb0ec76993..2e055934bd75 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -751,14 +751,18 @@ private: 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 e17d98d0f5c5..6743da01f1d3 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 com.android.internal.util.BitwiseOutputStream; import android.content.res.Resources; +import java.util.TimeZone; /** @@ -231,7 +232,7 @@ public final class BearerData { 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 000000000000..2cc2009147b3 --- /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 000000000000..19f38cabf23e --- /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 000000000000..3392b21a9728 --- /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 000000000000..a4a78c2642f6 --- /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 000000000000..f0e43ace6f9e --- /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 000000000000..a2427f514e17 --- /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 3b3cbf3e5516..49effa864905 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.TransactionUnavailableException; 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 @@ class SipSessionGroup implements SipListener { } 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()); - } - return expires; + int time = -1; + ContactHeader contact = (ContactHeader) response.getHeader(ContactHeader.NAME); + if (contact != null) { + time = contact.getExpires(); + } + 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) |