diff options
157 files changed, 4741 insertions, 1801 deletions
diff --git a/Android.mk b/Android.mk index d4d9a3374cb5..f752f4491923 100644 --- a/Android.mk +++ b/Android.mk @@ -364,6 +364,7 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS := \ -since ./frameworks/base/api/10.xml 10 \ -since ./frameworks/base/api/11.xml 11 \ -since ./frameworks/base/api/12.xml 12 \ + -since ./frameworks/base/api/13.xml 13 \ -werror -hide 113 \ -overview $(LOCAL_PATH)/core/java/overview.html @@ -414,6 +415,8 @@ web_docs_sample_code_flags := \ resources/samples/NotePad "Note Pad" \ -samplecode $(sample_dir)/SampleSyncAdapter \ resources/samples/SampleSyncAdapter "Sample Sync Adapter" \ + -samplecode $(sample_dir)/RandomMusicPlayer \ + resources/samples/RandomMusicPlayer "Random Music Player" \ -samplecode $(sample_dir)/RenderScript \ resources/samples/RenderScript "RenderScript" \ -samplecode $(sample_dir)/SearchableDictionary \ diff --git a/api/current.txt b/api/current.txt index 721e33e36291..b918149cabbc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21635,6 +21635,7 @@ package android.view { method public android.view.TextureView.SurfaceTextureListener getSurfaceTextureListener(); method public boolean isAvailable(); method protected final void onDraw(android.graphics.Canvas); + method public void setOpaque(boolean); method public void setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener); } @@ -21642,6 +21643,7 @@ package android.view { method public abstract void onSurfaceTextureAvailable(android.graphics.SurfaceTexture, int, int); method public abstract void onSurfaceTextureDestroyed(android.graphics.SurfaceTexture); method public abstract void onSurfaceTextureSizeChanged(android.graphics.SurfaceTexture, int, int); + method public abstract void onSurfaceTextureUpdated(android.graphics.SurfaceTexture); } public class TouchDelegate { @@ -24876,9 +24878,6 @@ package android.widget { } public static abstract class GridLayout.Alignment { - ctor public GridLayout.Alignment(); - method public abstract int getAlignmentValue(android.view.View, int, int); - method public int getSizeInCell(android.view.View, int, int, int); } public static class GridLayout.Group { @@ -27049,9 +27048,7 @@ package java.io { public final class FilePermission extends java.security.Permission implements java.io.Serializable { ctor public FilePermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -29810,8 +29807,8 @@ package java.net { ctor public DatagramSocket(java.net.SocketAddress) throws java.net.SocketException; method public void bind(java.net.SocketAddress) throws java.net.SocketException; method public void close(); - method public void connect(java.net.InetAddress, int); method public void connect(java.net.SocketAddress) throws java.net.SocketException; + method public void connect(java.net.InetAddress, int); method public void disconnect(); method public boolean getBroadcast() throws java.net.SocketException; method public java.nio.channels.DatagramChannel getChannel(); @@ -30299,9 +30296,7 @@ package java.net { public final class SocketPermission extends java.security.Permission implements java.io.Serializable { ctor public SocketPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -31308,9 +31303,7 @@ package java.security { public final class AllPermission extends java.security.Permission { ctor public AllPermission(java.lang.String, java.lang.String); ctor public AllPermission(); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -31324,9 +31317,7 @@ package java.security { public abstract class BasicPermission extends java.security.Permission implements java.io.Serializable { ctor public BasicPermission(java.lang.String); ctor public BasicPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -31689,10 +31680,8 @@ package java.security { public abstract class Permission implements java.security.Guard java.io.Serializable { ctor public Permission(java.lang.String); method public void checkGuard(java.lang.Object) throws java.lang.SecurityException; - method public abstract boolean equals(java.lang.Object); method public abstract java.lang.String getActions(); method public final java.lang.String getName(); - method public abstract int hashCode(); method public abstract boolean implies(java.security.Permission); method public java.security.PermissionCollection newPermissionCollection(); } @@ -31949,13 +31938,11 @@ package java.security { public final class UnresolvedPermission extends java.security.Permission implements java.io.Serializable { ctor public UnresolvedPermission(java.lang.String, java.lang.String, java.lang.String, java.security.cert.Certificate[]); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); method public java.lang.String getUnresolvedActions(); method public java.security.cert.Certificate[] getUnresolvedCerts(); method public java.lang.String getUnresolvedName(); method public java.lang.String getUnresolvedType(); - method public int hashCode(); method public boolean implies(java.security.Permission); } @@ -39471,11 +39458,9 @@ package javax.security.auth { public final class PrivateCredentialPermission extends java.security.Permission { ctor public PrivateCredentialPermission(java.lang.String, java.lang.String); - method public boolean equals(java.lang.Object); method public java.lang.String getActions(); method public java.lang.String getCredentialClass(); method public java.lang.String[][] getPrincipals(); - method public int hashCode(); method public boolean implies(java.security.Permission); } diff --git a/build/phone-hdpi-512-dalvik-heap.mk b/build/phone-hdpi-512-dalvik-heap.mk index 788b68609604..16e05055e030 100644 --- a/build/phone-hdpi-512-dalvik-heap.mk +++ b/build/phone-hdpi-512-dalvik-heap.mk @@ -19,5 +19,5 @@ PRODUCT_PROPERTY_OVERRIDES += \ dalvik.vm.heapstartsize=5m \ - dalvik.vm.heapgrowthlimit=32m \ + dalvik.vm.heapgrowthlimit=48m \ dalvik.vm.heapsize=128m diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp index b48be6ef3885..bbd1a1bfd60c 100644 --- a/cmds/keystore/keystore.cpp +++ b/cmds/keystore/keystore.cpp @@ -363,6 +363,7 @@ public: response = writeMasterKey(pw); } if (response == NO_ERROR) { + memcpy(mMasterKey, masterKeyBlob.getValue(), MASTER_KEY_SIZE_BYTES); setupMasterKeys(); } return response; diff --git a/cmds/keystore/test-keystore b/cmds/keystore/test-keystore new file mode 100755 index 000000000000..82b276fe7666 --- /dev/null +++ b/cmds/keystore/test-keystore @@ -0,0 +1,272 @@ +#!/bin/bash +# +# Copyright 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. + +set -e + +prefix=$0 +log_file=$prefix.log +baseline_file=$prefix.baseline + +function cleanup_output() { + rm -f $log_file + rm -f $baseline_file +} + +function log() { + echo "$@" + append $log_file \# "$@" + append $baseline_file \# "$@" +} + +function expect() { + append $baseline_file "$@" +} + +function append() { + declare -r file=$1 + shift + echo "$@" >> $file +} + +function run() { + # strip out carriage returns from adb + # strip out date/time from ls -l + "$@" | tr --delete '\r' | sed -E 's/[0-9]{4}-[0-9]{2}-[0-9]{2} +[0-9]{1,2}:[0-9]{2} //' >> $log_file +} + +function keystore() { + declare -r user=$1 + shift + run adb shell su $user keystore_cli "$@" +} + +function list_keystore_directory() { + run adb shell ls -al /data/misc/keystore +} + +function compare() { + log "comparing $baseline_file and $log_file" + diff $baseline_file $log_file || (log $tag FAILED && exit 1) +} + +function test_basic() { + + # + # reset + # + log "reset keystore as system user" + keystore system r + expect "1 No error" + list_keystore_directory + + # + # basic tests as system/root + # + log "root does not have permission to run test" + keystore root t + expect "6 Permission denied" + + log "but system user does" + keystore system t + expect "3 Uninitialized" + list_keystore_directory + + log "password is now bar" + keystore system p bar + expect "1 No error" + list_keystore_directory + expect "-rw------- keystore keystore 84 .masterkey" + + log "no error implies initialized and unlocked" + keystore system t + expect "1 No error" + + log "saw with no argument" + keystore system s + expect "5 Protocol error" + + log "saw nothing" + keystore system s "" + expect "1 No error" + + log "add key baz" + keystore system i baz quux + expect "1 No error" + + log "1000 is uid of system" + list_keystore_directory + expect "-rw------- keystore keystore 84 .masterkey" + expect "-rw------- keystore keystore 52 1000_baz" + + log "saw baz" + keystore system s "" + expect "1 No error" + expect "baz" + + log "system does not have access to read any keys" + keystore system g baz + expect "6 Permission denied" + + log "however, root can read system user keys (as can wifi or vpn users)" + keystore root g baz + expect "1 No error" + expect "quux" + + # + # app user tests + # + + # app_0 has uid 10000, as seen below + log "other uses cannot see the system keys" + keystore app_0 g baz + expect "7 Key not found" + + log "app user cannot use reset, password, lock, unlock" + keystore app_0 r + expect "6 Permission denied" + keystore app_0 p + expect "6 Permission denied" + keystore app_0 l + expect "6 Permission denied" + keystore app_0 u + expect "6 Permission denied" + + log "install app_0 key" + keystore app_0 i 0x deadbeef + expect 1 No error + list_keystore_directory + expect "-rw------- keystore keystore 84 .masterkey" + expect "-rw------- keystore keystore 52 10000_0x" + expect "-rw------- keystore keystore 52 1000_baz" + + log "get with no argument" + keystore app_0 g + expect "5 Protocol error" + + keystore app_0 g 0x + expect "1 No error" + expect "deadbeef" + + keystore app_0 i fred barney + expect "1 No error" + + keystore app_0 s "" + expect "1 No error" + expect "0x" + expect "fred" + + log "note that saw returns the suffix of prefix matches" + keystore app_0 s fr # fred + expect "1 No error" + expect "ed" # fred + + # + # lock tests + # + log "lock the store as system" + keystore system l + expect "1 No error" + keystore system t + expect "2 Locked" + + log "saw works while locked" + keystore app_0 s "" + expect "1 No error" + expect "0x" + expect "fred" + + log "...but cannot read keys..." + keystore app_0 g 0x + expect "2 Locked" + + log "...but they can be deleted." + keystore app_0 e 0x + expect "1 No error" + keystore app_0 d 0x + expect "1 No error" + keystore app_0 e 0x + expect "7 Key not found" + + # + # password + # + log "wrong password" + keystore system u foo + expect "13 Wrong password (4 tries left)" + log "right password" + keystore system u bar + expect "1 No error" + + log "make the password foo" + keystore system p foo + expect "1 No error" + + # + # final reset + # + log "reset wipes everything for all users" + keystore system r + expect "1 No error" + list_keystore_directory + + keystore system t + expect "3 Uninitialized" + +} + +function test_4599735() { + # http://b/4599735 + log "start regression test for b/4599735" + keystore system r + expect "1 No error" + + keystore system p foo + expect "1 No error" + + keystore system i baz quux + expect "1 No error" + + keystore root g baz + expect "1 No error" + expect "quux" + + keystore system l + expect "1 No error" + + keystore system p foo + expect "1 No error" + + log "after unlock, regression led to result of '8 Value corrupted'" + keystore root g baz + expect "1 No error" + expect "quux" + + keystore system r + expect "1 No error" + log "end regression test for b/4599735" +} + +function main() { + cleanup_output + log $tag START + test_basic + test_4599735 + compare + log $tag PASSED + cleanup_output +} + +main diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index ca771856d20d..d7b1e71c1d12 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -388,13 +388,15 @@ private: sp<MediaSource> mSource; StreamType mStreamType; + bool mSawFirstIDRFrame; DISALLOW_EVIL_CONSTRUCTORS(DetectSyncSource); }; DetectSyncSource::DetectSyncSource(const sp<MediaSource> &source) : mSource(source), - mStreamType(OTHER) { + mStreamType(OTHER), + mSawFirstIDRFrame(false) { const char *mime; CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); @@ -410,6 +412,8 @@ DetectSyncSource::DetectSyncSource(const sp<MediaSource> &source) } status_t DetectSyncSource::start(MetaData *params) { + mSawFirstIDRFrame = false; + return mSource->start(params); } @@ -439,16 +443,30 @@ static bool isIDRFrame(MediaBuffer *buffer) { status_t DetectSyncSource::read( MediaBuffer **buffer, const ReadOptions *options) { - status_t err = mSource->read(buffer, options); + for (;;) { + status_t err = mSource->read(buffer, options); - if (err != OK) { - return err; - } + if (err != OK) { + return err; + } - if (mStreamType == AVC && isIDRFrame(*buffer)) { - (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true); - } else { - (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true); + if (mStreamType == AVC) { + bool isIDR = isIDRFrame(*buffer); + (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, isIDR); + if (isIDR) { + mSawFirstIDRFrame = true; + } + } else { + (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true); + } + + if (mStreamType != AVC || mSawFirstIDRFrame) { + break; + } + + // Ignore everything up to the first IDR frame. + (*buffer)->release(); + *buffer = NULL; } return OK; @@ -945,6 +963,17 @@ int main(int argc, char **argv) { fprintf(stderr, "could not create extractor.\n"); return -1; } + + sp<MetaData> meta = extractor->getMetaData(); + + if (meta != NULL) { + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) { + syncInfoPresent = false; + } + } } size_t numTracks = extractor->countTracks(); diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java index 7a465c15e00f..491fcfea7991 100644 --- a/core/java/android/app/AlertDialog.java +++ b/core/java/android/app/AlertDialog.java @@ -890,7 +890,10 @@ public class AlertDialog extends Dialog implements DialogInterface { public AlertDialog create() { final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false); P.apply(dialog.mAlert); - dialog.setCanceledOnTouchOutside(P.mCancelable); + dialog.setCancelable(P.mCancelable); + if (P.mCancelable) { + dialog.setCanceledOnTouchOutside(true); + } dialog.setOnCancelListener(P.mOnCancelListener); if (P.mOnKeyListener != null) { dialog.setOnKeyListener(P.mOnKeyListener); diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index acf2f2f17d0c..1c9285e7a9ac 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -432,17 +432,17 @@ public class CompatibilityInfo implements Parcelable { // compatible with large screens, so diddle it. CompatibilityInfo.computeCompatibleScaling(inoutDm, inoutDm); } else { - inoutDm.widthPixels = inoutDm.unscaledWidthPixels; - inoutDm.heightPixels = inoutDm.unscaledHeightPixels; + inoutDm.widthPixels = inoutDm.noncompatWidthPixels; + inoutDm.heightPixels = inoutDm.noncompatHeightPixels; } if (isScalingRequired()) { float invertedRatio = applicationInvertedScale; - inoutDm.density *= invertedRatio; + inoutDm.density = inoutDm.noncompatDensity * invertedRatio; inoutDm.densityDpi = (int)((inoutDm.density*DisplayMetrics.DENSITY_DEFAULT)+.5f); - inoutDm.scaledDensity *= invertedRatio; - inoutDm.xdpi *= invertedRatio; - inoutDm.ydpi *= invertedRatio; + inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio; + inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio; + inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio; inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f); inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f); } @@ -471,8 +471,8 @@ public class CompatibilityInfo implements Parcelable { * @return Returns the scaling factor for the window. */ public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) { - final int width = dm.unscaledWidthPixels; - final int height = dm.unscaledHeightPixels; + final int width = dm.noncompatWidthPixels; + final int height = dm.noncompatHeightPixels; int shortSize, longSize; if (width < height) { shortSize = width; @@ -532,7 +532,9 @@ public class CompatibilityInfo implements Parcelable { sb.append(applicationDensity); sb.append("dpi"); if (isScalingRequired()) { - sb.append(" scaling"); + sb.append(" "); + sb.append(applicationScale); + sb.append("x"); } if (!supportsScreen()) { sb.append(" resizing"); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index bd8b1a4af936..324c9fdea6db 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1416,23 +1416,19 @@ public class Resources { } if (metrics != null) { mMetrics.setTo(metrics); - // NOTE: We should re-arrange this code to create a Display - // with the CompatibilityInfo that is used everywhere we deal - // with the display in relation to this app, rather than - // doing the conversion here. This impl should be okay because - // we make sure to return a compatible display in the places - // where there are public APIs to retrieve the display... but - // it would be cleaner and more maintainble to just be - // consistently dealing with a compatible display everywhere in - // the framework. - if (mCompatibilityInfo != null) { - mCompatibilityInfo.applyToDisplayMetrics(mMetrics); - } } + // NOTE: We should re-arrange this code to create a Display + // with the CompatibilityInfo that is used everywhere we deal + // with the display in relation to this app, rather than + // doing the conversion here. This impl should be okay because + // we make sure to return a compatible display in the places + // where there are public APIs to retrieve the display... but + // it would be cleaner and more maintainble to just be + // consistently dealing with a compatible display everywhere in + // the framework. if (mCompatibilityInfo != null) { mCompatibilityInfo.applyToDisplayMetrics(mMetrics); } - mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; int configChanges = 0xfffffff; if (config != null) { mTmpConfig.setTo(config); @@ -1448,6 +1444,7 @@ public class Resources { if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); } + mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; String locale = null; if (mConfiguration.locale != null) { diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 2b9c082d5696..9bab797b4c54 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -82,11 +82,8 @@ interface IUsbManager /* Clears default preferences and permissions for the package */ void clearDefaults(String packageName); - /* Sets the current primary USB function. */ - void setPrimaryFunction(String functions); - - /* Sets the default primary USB function. */ - void setDefaultFunction(String functions); + /* Sets the current USB function. */ + void setCurrentFunction(String function, boolean makeDefault); /* Sets the file path for USB mass storage backing file. */ void setMassStorageBackingFile(String path); diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index a828a23b2f44..67d200cd40ff 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -408,32 +408,18 @@ public class UsbManager { } /** - * Sets the primary USB function. + * Sets the current USB function. * * @param function name of the USB function + * @param makeDefault true if this should be set as the default * * {@hide} */ - public void setPrimaryFunction(String function) { + public void setCurrentFunction(String function, boolean makeDefault) { try { - mService.setPrimaryFunction(function); + mService.setCurrentFunction(function, makeDefault); } catch (RemoteException e) { - Log.e(TAG, "RemoteException in setPrimaryFunction", e); - } - } - - /** - * Sets the default primary USB function. - * - * @param function name of the USB function - * - * {@hide} - */ - public void setDefaultFunction(String function) { - try { - mService.setDefaultFunction(function); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in setDefaultFunction", e); + Log.e(TAG, "RemoteException in setCurrentFunction", e); } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 2242e9e8e178..ce6f697be6be 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -164,6 +164,12 @@ public class ConnectivityManager { public static final String EXTRA_ERRORED_TETHER = "erroredArray"; /** + * The absence of APN.. + * @hide + */ + public static final int TYPE_NONE = -1; + + /** * The Default Mobile data connection. When active, all data traffic * will use this connection by default. */ diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index c49c019b50f9..445b2f77b06c 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -117,13 +117,14 @@ public class DhcpStateMachine extends StateMachine { PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG); + mDhcpRenewWakeLock.setReferenceCounted(false); mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //DHCP renew if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this); - //acquire a 40s wakelock to finish DHCP renewal + //Lock released after 40s in worst case scenario mDhcpRenewWakeLock.acquire(40000); sendMessage(CMD_RENEW_DHCP); } @@ -166,6 +167,7 @@ public class DhcpStateMachine extends StateMachine { switch (message.what) { case CMD_RENEW_DHCP: Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName); + mDhcpRenewWakeLock.release(); break; case SM_QUIT_CMD: mContext.unregisterReceiver(mBroadcastReceiver); @@ -268,10 +270,12 @@ public class DhcpStateMachine extends StateMachine { /* Notify controller before starting DHCP */ mController.sendMessage(CMD_PRE_DHCP_ACTION); transitionTo(mWaitBeforeRenewalState); + //mDhcpRenewWakeLock is released in WaitBeforeRenewalState } else { if (!runDhcp(DhcpAction.RENEW)) { transitionTo(mStoppedState); } + mDhcpRenewWakeLock.release(); } break; case CMD_START_DHCP: @@ -318,6 +322,10 @@ public class DhcpStateMachine extends StateMachine { } return retValue; } + @Override + public void exit() { + mDhcpRenewWakeLock.release(); + } } private boolean runDhcp(DhcpAction dhcpAction) { diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/core/java/android/nfc/INfcAdapterExtras.aidl index 3f8b8563b6af..0c2a2fd065b2 100755 --- a/core/java/android/nfc/INfcAdapterExtras.aidl +++ b/core/java/android/nfc/INfcAdapterExtras.aidl @@ -16,7 +16,6 @@ package android.nfc; -import android.nfc.ApduList; import android.os.Bundle; @@ -29,4 +28,5 @@ interface INfcAdapterExtras { Bundle transceive(in byte[] data_in); int getCardEmulationRoute(); void setCardEmulationRoute(int route); + void authenticate(in byte[] token); } diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index 5e99463640d2..1b09242d4c03 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -226,4 +226,6 @@ interface INetworkManagementService */ int getInterfaceTxThrottle(String iface); + void setBandwidthControlEnabled(boolean enabled); + } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 603edf0fac7e..afff7e24f04f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -300,6 +300,21 @@ public final class Settings { "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; /** + * Activity Action: Show a dialog to select input method. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SHOW_INPUT_METHOD_PICKER = + "android.settings.SHOW_INPUT_METHOD_PICKER"; + + /** * Activity Action: Show settings to manage the user input dictionary. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -3789,6 +3804,8 @@ public final class Settings { "dream_timeout"; /** {@hide} */ + public static final String NETSTATS_ENABLED = "netstats_enabled"; + /** {@hide} */ public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval"; /** {@hide} */ public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold"; diff --git a/core/java/android/server/BluetoothBondState.java b/core/java/android/server/BluetoothBondState.java index 39c3c8854533..5fa8836c1d9c 100644 --- a/core/java/android/server/BluetoothBondState.java +++ b/core/java/android/server/BluetoothBondState.java @@ -79,7 +79,6 @@ class BluetoothBondState { mService = service; mBluetoothInputProfileHandler = BluetoothInputProfileHandler.getInstance(mContext, mService); - getProfileProxy(); } synchronized void setPendingOutgoingBonding(String address) { @@ -109,6 +108,7 @@ class BluetoothBondState { mState.put(mService.getAddressFromObjectPath(device).toUpperCase(), BluetoothDevice.BOND_BONDED); } + getProfileProxy(); } public synchronized void setBondState(String address, int state) { diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index d594567d09d2..519b98056b9f 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -119,13 +119,37 @@ public class DisplayMetrics { * being applied. * @hide */ - public int unscaledWidthPixels; + public int noncompatWidthPixels; /** * The reported display height prior to any compatibility mode scaling * being applied. * @hide */ - public int unscaledHeightPixels; + public int noncompatHeightPixels; + /** + * The reported display density prior to any compatibility mode scaling + * being applied. + * @hide + */ + public float noncompatDensity; + /** + * The reported scaled density prior to any compatibility mode scaling + * being applied. + * @hide + */ + public float noncompatScaledDensity; + /** + * The reported display xdpi prior to any compatibility mode scaling + * being applied. + * @hide + */ + public float noncompatXdpi; + /** + * The reported display ydpi prior to any compatibility mode scaling + * being applied. + * @hide + */ + public float noncompatYdpi; public DisplayMetrics() { } @@ -138,8 +162,12 @@ public class DisplayMetrics { scaledDensity = o.scaledDensity; xdpi = o.xdpi; ydpi = o.ydpi; - unscaledWidthPixels = o.unscaledWidthPixels; - unscaledHeightPixels = o.unscaledHeightPixels; + noncompatWidthPixels = o.noncompatWidthPixels; + noncompatHeightPixels = o.noncompatHeightPixels; + noncompatDensity = o.noncompatDensity; + noncompatScaledDensity = o.noncompatScaledDensity; + noncompatXdpi = o.noncompatXdpi; + noncompatYdpi = o.noncompatYdpi; } public void setToDefaults() { @@ -150,8 +178,8 @@ public class DisplayMetrics { scaledDensity = density; xdpi = DENSITY_DEVICE; ydpi = DENSITY_DEVICE; - unscaledWidthPixels = 0; - unscaledHeightPixels = 0; + noncompatWidthPixels = 0; + noncompatHeightPixels = 0; } @Override diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 3fa8dfdb7c53..2be5a4955981 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -107,8 +107,8 @@ public class Display { CompatibilityInfo ci; if (doCompat && (ci=mCompatibilityInfo.getIfNeeded()) != null) { synchronized (mTmpMetrics) { - mTmpMetrics.unscaledWidthPixels = outSize.x; - mTmpMetrics.unscaledHeightPixels = outSize.y; + mTmpMetrics.noncompatWidthPixels = outSize.x; + mTmpMetrics.noncompatHeightPixels = outSize.y; mTmpMetrics.density = mDensity; ci.applyToDisplayMetrics(mTmpMetrics); outSize.x = mTmpMetrics.widthPixels; @@ -268,14 +268,15 @@ public class Display { } private void getNonSizeMetrics(DisplayMetrics outMetrics) { - outMetrics.density = mDensity; outMetrics.densityDpi = (int)((mDensity*DisplayMetrics.DENSITY_DEFAULT)+.5f); - outMetrics.scaledDensity= outMetrics.density; - outMetrics.xdpi = mDpiX; - outMetrics.ydpi = mDpiY; - outMetrics.unscaledWidthPixels = outMetrics.widthPixels; - outMetrics.unscaledHeightPixels = outMetrics.heightPixels; + outMetrics.noncompatWidthPixels = outMetrics.widthPixels; + outMetrics.noncompatHeightPixels = outMetrics.heightPixels; + + outMetrics.density = outMetrics.noncompatDensity = mDensity; + outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density; + outMetrics.xdpi = outMetrics.noncompatXdpi = mDpiX; + outMetrics.ydpi = outMetrics.noncompatYdpi = mDpiY; } static IWindowManager getWindowManager() { diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 5216c49c8cdd..04f35dc7190e 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -161,10 +161,11 @@ class GLES20Canvas extends HardwareCanvas { // Hardware layers /////////////////////////////////////////////////////////////////////////// - static native int nCreateTextureLayer(int[] layerInfo); + static native int nCreateTextureLayer(boolean opaque, int[] layerInfo); static native int nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo); static native void nResizeLayer(int layerId, int width, int height, int[] layerInfo); - static native void nUpdateTextureLayer(int layerId, int width, int height, SurfaceTexture surface); + static native void nUpdateTextureLayer(int layerId, int width, int height, boolean opaque, + SurfaceTexture surface); static native void nDestroyLayer(int layerId); static native void nDestroyLayerDeferred(int layerId); static native boolean nCopyLayer(int layerId, int bitmap); diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java index 063eee76b234..3ac445987dbd 100644 --- a/core/java/android/view/GLES20TextureLayer.java +++ b/core/java/android/view/GLES20TextureLayer.java @@ -28,9 +28,9 @@ class GLES20TextureLayer extends GLES20Layer { private int mTexture; private SurfaceTexture mSurface; - GLES20TextureLayer() { + GLES20TextureLayer(boolean isOpaque) { int[] layerInfo = new int[2]; - mLayer = GLES20Canvas.nCreateTextureLayer(layerInfo); + mLayer = GLES20Canvas.nCreateTextureLayer(isOpaque, layerInfo); if (mLayer != 0) { mTexture = layerInfo[0]; @@ -70,7 +70,7 @@ class GLES20TextureLayer extends GLES20Layer { return mSurface; } - void update(int width, int height) { - GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, mSurface); + void update(int width, int height, boolean isOpaque) { + GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, isOpaque, mSurface); } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 5ceb12ab127f..c6d011e16353 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -170,9 +170,11 @@ public abstract class HardwareRenderer { * Creates a new hardware layer. A hardware layer built by calling this * method will be treated as a texture layer, instead of as a render target. * + * @param isOpaque Whether the layer should be opaque or not + * * @return A hardware layer */ - abstract HardwareLayer createHardwareLayer(); + abstract HardwareLayer createHardwareLayer(boolean isOpaque); /** * Creates a new hardware layer. @@ -202,8 +204,9 @@ public abstract class HardwareRenderer { * @param layer The hardware layer to update * @param width The layer's width * @param height The layer's height + * @param isOpaque Whether the layer is opaque */ - abstract void updateTextureLayer(HardwareLayer layer, int width, int height); + abstract void updateTextureLayer(HardwareLayer layer, int width, int height, boolean isOpaque); /** * Copies the content of the specified layer into the specified bitmap. @@ -803,8 +806,8 @@ public abstract class HardwareRenderer { } @Override - HardwareLayer createHardwareLayer() { - return new GLES20TextureLayer(); + HardwareLayer createHardwareLayer(boolean isOpaque) { + return new GLES20TextureLayer(isOpaque); } @Override @@ -818,8 +821,8 @@ public abstract class HardwareRenderer { } @Override - void updateTextureLayer(HardwareLayer layer, int width, int height) { - ((GLES20TextureLayer) layer).update(width, height); + void updateTextureLayer(HardwareLayer layer, int width, int height, boolean isOpaque) { + ((GLES20TextureLayer) layer).update(width, height, isOpaque); } @Override diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 042120587dea..de398ebaa4b4 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -67,15 +67,19 @@ import android.util.Log; * // Something bad happened * } * } - * + * * public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { * // Ignored, Camera does all the work for us * } - * + * * public void onSurfaceTextureDestroyed(SurfaceTexture surface) { * mCamera.stopPreview(); * mCamera.release(); * } + * + * public void onSurfaceTextureUpdated(SurfaceTexture surface) { + * // Ignored + * } * } * </pre> * @@ -96,6 +100,8 @@ public class TextureView extends View { private SurfaceTexture mSurface; private SurfaceTextureListener mListener; + private boolean mOpaque = true; + private final Runnable mUpdateLayerAction = new Runnable() { @Override public void run() { @@ -146,6 +152,28 @@ public class TextureView extends View { mLayerPaint = new Paint(); } + /** + * {@inheritDoc} + */ + @Override + public boolean isOpaque() { + return mOpaque; + } + + /** + * Indicates whether the content of this TextureView is opaque. The + * content is assumed to be opaque by default. + * + * @param opaque True if the content of this TextureView is opaque, + * false otherwise + */ + public void setOpaque(boolean opaque) { + if (opaque != mOpaque) { + mOpaque = opaque; + if (mLayer != null) updateLayer(); + } + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -215,7 +243,6 @@ public class TextureView extends View { */ @Override public final void draw(Canvas canvas) { - super.draw(canvas); } /** @@ -246,7 +273,7 @@ public class TextureView extends View { } if (mLayer == null) { - mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(); + mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque); mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer); nSetDefaultBufferSize(mSurface, getWidth(), getHeight()); @@ -290,7 +317,11 @@ public class TextureView extends View { return; } - mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight()); + mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight(), mOpaque); + + if (mListener != null) { + mListener.onSurfaceTextureUpdated(mSurface); + } invalidate(); } @@ -451,6 +482,14 @@ public class TextureView extends View { * @param surface The surface about to be destroyed */ public void onSurfaceTextureDestroyed(SurfaceTexture surface); + + /** + * Invoked when the specified {@link SurfaceTexture} is updated through + * {@link SurfaceTexture#updateTexImage()}. + * + * @param surface The surface just updated + */ + public void onSurfaceTextureUpdated(SurfaceTexture surface); } private static native void nSetDefaultBufferSize(SurfaceTexture surfaceTexture, diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f70ca90222d4..8184643cd160 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -9674,10 +9674,10 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit computeScroll(); canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID; + mPrivateFlags &= ~DIRTY_MASK; // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { - mPrivateFlags &= ~DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java index afbedafa6eac..ad660c1e209d 100644 --- a/core/java/android/view/ViewAncestor.java +++ b/core/java/android/view/ViewAncestor.java @@ -288,6 +288,10 @@ public final class ViewAncestor extends Handler implements ViewParent, private final int mDensity; + // This flag tracks when the mIgnoreDirtyState flag is set during draw(), to avoid + // clearing that flag prematurely + private boolean mSetIgnoreDirtyState = false; + /** * Consistency verifier for debugging purposes. */ @@ -672,6 +676,7 @@ public final class ViewAncestor extends Handler implements ViewParent, } } if (!mDirty.isEmpty() && !mDirty.contains(dirty)) { + mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } mDirty.union(dirty); @@ -1000,8 +1005,7 @@ public final class ViewAncestor extends Handler implements ViewParent, + "x" + desiredWindowHeight + "..."); boolean goodMeasure = false; - if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT - || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { + if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { // On large screens, we don't want to allow dialogs to just // stretch to fill the entire width of the screen to display // one line of text. First try doing the layout at a smaller @@ -1748,7 +1752,7 @@ public final class ViewAncestor extends Handler implements ViewParent, mAttachInfo.mIgnoreDirtyState = true; dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } - + if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { if (!dirty.isEmpty() || mIsAnimating) { mIsAnimating = false; @@ -1878,9 +1882,13 @@ public final class ViewAncestor extends Handler implements ViewParent, } canvas.setScreenDensity(scalingRequired ? DisplayMetrics.DENSITY_DEVICE : 0); + mSetIgnoreDirtyState = false; mView.draw(canvas); } finally { - mAttachInfo.mIgnoreDirtyState = false; + if (!mSetIgnoreDirtyState) { + // Only clear the flag if it was not set during the mView.draw() call + mAttachInfo.mIgnoreDirtyState = false; + } } if (false && ViewDebug.consistencyCheckEnabled) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e928f802b55f..b9474e60f02e 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1233,6 +1233,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * {@inheritDoc} */ + @SuppressWarnings({"ConstantConditions"}) @Override protected boolean dispatchHoverEvent(MotionEvent event) { final int action = event.getAction(); @@ -3876,8 +3877,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (!childMatrix.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); + //boundingRect.inset(-0.5f, -0.5f); childMatrix.mapRect(boundingRect); - dirty.set((int) boundingRect.left, (int) boundingRect.top, + dirty.set((int) (boundingRect.left - 0.5f), + (int) (boundingRect.top - 0.5f), (int) (boundingRect.right + 0.5f), (int) (boundingRect.bottom + 0.5f)); } diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java index b5d493368844..bb4d192516b9 100644 --- a/core/java/android/webkit/JniUtil.java +++ b/core/java/android/webkit/JniUtil.java @@ -23,6 +23,10 @@ import android.util.Log; import java.io.InputStream; class JniUtil { + + static { + System.loadLibrary("webcore"); + } private static final String LOGTAG = "webkit"; private JniUtil() {} // Utility class, do not instantiate. diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 06a61bdceb21..3a09e3b5b2a5 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -2358,7 +2358,7 @@ public final class WebViewCore { mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale = mViewportInitialScale / 100.0f; } else if (mViewportWidth > 0 && mViewportWidth < webViewWidth && - !mWebView.getSettings().getUseFixedViewport()) { + !getSettings().getUseFixedViewport()) { mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale = (float) webViewWidth / mViewportWidth; } else { diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index 0e528693ec79..15702244596f 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -535,7 +535,7 @@ public class GridLayout extends ViewGroup { return result; } - private int getDefaultMargin(View c, boolean leading, boolean horizontal) { + private int getDefaultMargin(View c, boolean horizontal, boolean leading) { // In the absence of any other information, calculate a default gap such // that, in a grid of identical components, the heights and the vertical // gaps are in the proportion of the golden ratio. @@ -544,12 +544,12 @@ public class GridLayout extends ViewGroup { return (int) (c.getMeasuredHeight() / GOLDEN_RATIO / 2); } - private int getDefaultMargin(View c, boolean isAtEdge, boolean leading, boolean horizontal) { + private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) { // todo remove DEFAULT_CONTAINER_MARGIN. Use padding? Seek advice on Themes/Styles, etc. - return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, leading, horizontal); + return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, horizontal, leading); } - private int getDefaultMarginValue(View c, LayoutParams p, boolean leading, boolean horizontal) { + private int getDefaultMarginValue(View c, LayoutParams p, boolean horizontal, boolean leading) { if (!mUseDefaultMargins) { return 0; } @@ -558,15 +558,19 @@ public class GridLayout extends ViewGroup { Interval span = group.span; boolean isAtEdge = leading ? (span.min == 0) : (span.max == axis.getCount()); - return getDefaultMargin(c, isAtEdge, leading, horizontal); + return getDefaultMargin(c, isAtEdge, horizontal, leading); } - private int getMargin(View view, boolean leading, boolean horizontal) { + private int getMargin(View view, boolean horizontal, boolean leading) { LayoutParams lp = getLayoutParams(view); int margin = horizontal ? (leading ? lp.leftMargin : lp.rightMargin) : (leading ? lp.topMargin : lp.bottomMargin); - return margin == UNDEFINED ? getDefaultMarginValue(view, lp, leading, horizontal) : margin; + return margin == UNDEFINED ? getDefaultMarginValue(view, lp, horizontal, leading) : margin; + } + + private int getTotalMargin(View child, boolean horizontal) { + return getMargin(child, horizontal, true) + getMargin(child, horizontal, false); } private static int valueIfDefined(int value, int defaultValue) { @@ -749,8 +753,8 @@ public class GridLayout extends ViewGroup { View c = getChildAt(i); drawRectangle(canvas, c.getLeft() - getMargin(c, true, true), - c.getTop() - getMargin(c, true, false), - c.getRight() + getMargin(c, false, true), + c.getTop() - getMargin(c, false, true), + c.getRight() + getMargin(c, true, false), c.getBottom() + getMargin(c, false, false), paint); } } @@ -794,17 +798,12 @@ public class GridLayout extends ViewGroup { return c.getVisibility() == View.GONE; } - private void measureChildWithMargins(View child, - int parentWidthMeasureSpec, int parentHeightMeasureSpec) { - + private void measureChildWithMargins(View child, int widthMeasureSpec, int heightMeasureSpec) { LayoutParams lp = getLayoutParams(child); - int hMargins = getMargin(child, true, true) + getMargin(child, false, true); - int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, - mPaddingLeft + mPaddingRight + hMargins, lp.width); - int vMargins = getMargin(child, true, false) + getMargin(child, false, false); - int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, - mPaddingTop + mPaddingBottom + vMargins, lp.height); - + int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, + mPaddingLeft + mPaddingRight + getTotalMargin(child, true), lp.width); + int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, + mPaddingTop + mPaddingBottom + getTotalMargin(child, false), lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } @@ -842,9 +841,7 @@ public class GridLayout extends ViewGroup { private int getMeasurementIncludingMargin(View c, boolean horizontal, int measurementType) { int result = getMeasurement(c, horizontal, measurementType); if (mAlignmentMode == ALIGN_MARGINS) { - int leadingMargin = getMargin(c, true, horizontal); - int trailingMargin = getMargin(c, false, horizontal); - return result + leadingMargin + trailingMargin; + return result + getTotalMargin(c, horizontal); } return result; } @@ -919,8 +916,8 @@ public class GridLayout extends ViewGroup { if (mAlignmentMode == ALIGN_MARGINS) { int leftMargin = getMargin(c, true, true); - int topMargin = getMargin(c, true, false); - int rightMargin = getMargin(c, false, true); + int topMargin = getMargin(c, false, true); + int rightMargin = getMargin(c, true, false); int bottomMargin = getMargin(c, false, false); // Same calculation as getMeasurementIncludingMargin() @@ -928,8 +925,8 @@ public class GridLayout extends ViewGroup { int mHeight = topMargin + pHeight + bottomMargin; // Alignment offsets: the location of the view relative to its alignment group. - int a2vx = colBounds.before - hAlign.getAlignmentValue(c, mWidth, type); - int a2vy = rowBounds.before - vAlign.getAlignmentValue(c, mHeight, type); + int a2vx = colBounds.getOffset(c, hAlign, type, mWidth); + int a2vy = rowBounds.getOffset(c, vAlign, type, mHeight); dx = c2ax + a2vx + leftMargin; dy = c2ay + a2vy + topMargin; @@ -938,8 +935,8 @@ public class GridLayout extends ViewGroup { cellHeight -= topMargin + bottomMargin; } else { // Alignment offsets: the location of the view relative to its alignment group. - int a2vx = colBounds.before - hAlign.getAlignmentValue(c, pWidth, type); - int a2vy = rowBounds.before - vAlign.getAlignmentValue(c, pHeight, type); + int a2vx = colBounds.getOffset(c, hAlign, type, pWidth); + int a2vy = rowBounds.getOffset(c, vAlign, type, pHeight); dx = c2ax + a2vx; dy = c2ay + a2vy; @@ -1051,7 +1048,7 @@ public class GridLayout extends ViewGroup { Group group = horizontal ? lp.columnGroup : lp.rowGroup; groups[i] = group; - bounds[i] = new Bounds(); + bounds[i] = group.alignment.getBounds(); } return new PackedMap<Group, Bounds>(groups, bounds); @@ -1387,7 +1384,7 @@ public class GridLayout extends ViewGroup { Group g = horizontal ? lp.columnGroup : lp.rowGroup; Interval span = g.span; int index = leading ? span.min : span.max; - margins[index] = max(margins[index], getMargin(c, leading, horizontal)); + margins[index] = max(margins[index], getMargin(c, horizontal, leading)); } } @@ -1817,7 +1814,8 @@ public class GridLayout extends ViewGroup { } private int getDefaultWeight(int size) { - return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0; + //return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0; + return DEFAULT_WEIGHT_0; } private void init(Context context, AttributeSet attrs, int defaultGravity) { @@ -1996,20 +1994,24 @@ public class GridLayout extends ViewGroup { reset(); } - private void reset() { + protected void reset() { before = Integer.MIN_VALUE; after = Integer.MIN_VALUE; } - private void include(int before, int after) { + protected void include(int before, int after) { this.before = max(this.before, before); this.after = max(this.after, after); } - private int size() { + protected int size() { return before + after; } + protected int getOffset(View c, Alignment alignment, int type, int size) { + return before - alignment.getAlignmentValue(c, size, type); + } + @Override public String toString() { return "Bounds{" + @@ -2235,15 +2237,18 @@ public class GridLayout extends ViewGroup { * {@link Group#alignment alignment}. Overall placement of the view in the cell * group is specified by the two alignments which act along each axis independently. * <p> + * The GridLayout class defines the most common alignments used in general layout: + * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link + * #BASELINE} and {@link #FILL}. + */ + /* * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)}, * to return the appropriate value for the type of alignment being defined. * The enclosing algorithms position the children * so that the locations defined by the alignment values * are the same for all of the views in a group. * <p> - * The GridLayout class defines the most common alignments used in general layout: - * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link - * #BASELINE} and {@link #FILL}. + */ public static abstract class Alignment { private static final Alignment GONE = new Alignment() { @@ -2253,6 +2258,9 @@ public class GridLayout extends ViewGroup { } }; + /*pp*/ Alignment() { + } + /** * Returns an alignment value. In the case of vertical alignments the value * returned should indicate the distance from the top of the view to the @@ -2266,7 +2274,7 @@ public class GridLayout extends ViewGroup { * * @return the alignment value */ - public abstract int getAlignmentValue(View view, int viewSize, int measurementType); + /*pp*/ abstract int getAlignmentValue(View view, int viewSize, int measurementType); /** * Returns the size of the view specified by this alignment. @@ -2283,9 +2291,13 @@ public class GridLayout extends ViewGroup { * * @return the aligned size */ - public int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) { + /*pp*/ int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) { return viewSize; } + + /*pp*/ Bounds getBounds() { + return new Bounds(); + } } private static final Alignment LEADING = new Alignment() { @@ -2349,11 +2361,42 @@ public class GridLayout extends ViewGroup { return UNDEFINED; } int baseline = view.getBaseline(); - if (baseline == -1) { - return UNDEFINED; - } else { - return baseline; - } + return (baseline == -1) ? UNDEFINED : baseline; + } + + @Override + public Bounds getBounds() { + return new Bounds() { + /* + In a baseline aligned row in which some components define a baseline + and some don't, we need a third variable to properly account for all + the sizes. This tracks the maximum size of all the components - + including those that don't define a baseline. + */ + private int size; + + @Override + protected void reset() { + super.reset(); + size = 0; + } + + @Override + protected void include(int before, int after) { + super.include(before, after); + size = max(size, before + after); + } + + @Override + protected int size() { + return max(super.size(), size); + } + + @Override + protected int getOffset(View c, Alignment alignment, int type, int size) { + return max(0, super.getOffset(c, alignment, type, size)); + } + }; } }; diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index c475eff94283..6204b4eb19ff 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -50,6 +50,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.Window; +import android.view.View.MeasureSpec; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; @@ -742,8 +743,14 @@ public class ActionBarView extends AbsActionBarView { View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout; if (homeLayout.getVisibility() != GONE) { - homeLayout.measure( - MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), + final LayoutParams lp = homeLayout.getLayoutParams(); + int homeWidthSpec; + if (lp.width < 0) { + homeWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST); + } else { + homeWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY); + } + homeLayout.measure(homeWidthSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); final int homeWidth = homeLayout.getMeasuredWidth(); availableWidth = Math.max(0, availableWidth - homeWidth); @@ -1120,6 +1127,34 @@ public class ActionBarView extends AbsActionBarView { width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin; height = Math.max(height, iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + final int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + switch (widthMode) { + case MeasureSpec.AT_MOST: + width = Math.min(width, widthSize); + break; + case MeasureSpec.EXACTLY: + width = widthSize; + break; + case MeasureSpec.UNSPECIFIED: + default: + break; + } + switch (heightMode) { + case MeasureSpec.AT_MOST: + height = Math.min(height, heightSize); + break; + case MeasureSpec.EXACTLY: + height = heightSize; + break; + case MeasureSpec.UNSPECIFIED: + default: + break; + } setMeasuredDimension(width, height); } diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index cbb110a09120..97bbe5218b70 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -103,7 +103,8 @@ public class LockPatternView extends View { private boolean mTactileFeedbackEnabled = true; private boolean mPatternInProgress = false; - private float mDiameterFactor = 0.5f; + private float mDiameterFactor = 0.10f; // TODO: move to attrs + private final int mStrokeAlpha = 128; private float mHitFactor = 0.6f; private float mSquareWidth; @@ -133,6 +134,7 @@ public class LockPatternView extends View { private final Matrix mArrowMatrix = new Matrix(); private final Matrix mCircleMatrix = new Matrix(); + /** * Represents a cell in the 3 X 3 matrix of the unlock pattern view. */ @@ -267,17 +269,17 @@ public class LockPatternView extends View { mPathPaint.setAntiAlias(true); mPathPaint.setDither(true); mPathPaint.setColor(Color.WHITE); // TODO this should be from the style - mPathPaint.setAlpha(128); + mPathPaint.setAlpha(mStrokeAlpha); mPathPaint.setStyle(Paint.Style.STROKE); mPathPaint.setStrokeJoin(Paint.Join.ROUND); mPathPaint.setStrokeCap(Paint.Cap.ROUND); // lot's of bitmaps! - mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default); - mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched); - mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default); - mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green); - mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red); + mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_holo); + mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_holo); + mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default_holo); + mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green_holo); + mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red_holo); mBitmapArrowGreenUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_green_up); mBitmapArrowRedUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_red_up); @@ -464,8 +466,7 @@ public class LockPatternView extends View { break; case MeasureSpec.EXACTLY: default: - // use the specified size, if non-zero - result = specSize != 0 ? specSize : desired; + result = specSize; } return result; } @@ -890,41 +891,6 @@ public class LockPatternView extends View { final Path currentPath = mCurrentPath; currentPath.rewind(); - // TODO: the path should be created and cached every time we hit-detect a cell - // only the last segment of the path should be computed here - // draw the path of the pattern (unless the user is in progress, and - // we are in stealth mode) - final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong); - if (drawPath) { - boolean anyCircles = false; - for (int i = 0; i < count; i++) { - Cell cell = pattern.get(i); - - // only draw the part of the pattern stored in - // the lookup table (this is only different in the case - // of animation). - if (!drawLookup[cell.row][cell.column]) { - break; - } - anyCircles = true; - - float centerX = getCenterXForColumn(cell.column); - float centerY = getCenterYForRow(cell.row); - if (i == 0) { - currentPath.moveTo(centerX, centerY); - } else { - currentPath.lineTo(centerX, centerY); - } - } - - // add last in progress section - if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate) - && anyCircles) { - currentPath.lineTo(mInProgressX, mInProgressY); - } - canvas.drawPath(currentPath, mPathPaint); - } - // draw the circles final int paddingTop = mPaddingTop; final int paddingLeft = mPaddingLeft; @@ -938,6 +904,12 @@ public class LockPatternView extends View { } } + // TODO: the path should be created and cached every time we hit-detect a cell + // only the last segment of the path should be computed here + // draw the path of the pattern (unless the user is in progress, and + // we are in stealth mode) + final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong); + // draw the arrows associated with the path (unless the user is in progress, and // we are in stealth mode) boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0; @@ -960,6 +932,37 @@ public class LockPatternView extends View { drawArrow(canvas, leftX, topY, cell, next); } } + + if (drawPath) { + boolean anyCircles = false; + for (int i = 0; i < count; i++) { + Cell cell = pattern.get(i); + + // only draw the part of the pattern stored in + // the lookup table (this is only different in the case + // of animation). + if (!drawLookup[cell.row][cell.column]) { + break; + } + anyCircles = true; + + float centerX = getCenterXForColumn(cell.column); + float centerY = getCenterYForRow(cell.row); + if (i == 0) { + currentPath.moveTo(centerX, centerY); + } else { + currentPath.lineTo(centerX, centerY); + } + } + + // add last in progress section + if ((mPatternInProgress || mPatternDisplayMode == DisplayMode.Animate) + && anyCircles) { + currentPath.lineTo(mInProgressX, mInProgressY); + } + canvas.drawPath(currentPath, mPathPaint); + } + mPaint.setFilterBitmap(oldFlag); // restore default flag } diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index e301e44c04bf..aff439418327 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -608,8 +608,8 @@ static OpenGLRenderer* android_view_GLES20Canvas_createLayerRenderer(JNIEnv* env } static Layer* android_view_GLES20Canvas_createTextureLayer(JNIEnv* env, jobject clazz, - jintArray layerInfo) { - Layer* layer = LayerRenderer::createTextureLayer(); + jboolean isOpaque, jintArray layerInfo) { + Layer* layer = LayerRenderer::createTextureLayer(isOpaque); if (layer) { jint* storage = env->GetIntArrayElements(layerInfo, NULL); @@ -645,15 +645,17 @@ static void android_view_GLES20Canvas_resizeLayer(JNIEnv* env, jobject clazz, } static void android_view_GLES20Canvas_updateTextureLayer(JNIEnv* env, jobject clazz, - Layer* layer, jint width, jint height, jobject surface) { + Layer* layer, jint width, jint height, jboolean isOpaque, jobject surface) { float transform[16]; sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, surface)); - surfaceTexture->updateTexImage(); + while (surfaceTexture->getQueuedCount() > 0) { + surfaceTexture->updateTexImage(); + } surfaceTexture->getTransformMatrix(transform); GLenum renderTarget = surfaceTexture->getCurrentTextureTarget(); - LayerRenderer::updateTextureLayer(layer, width, height, renderTarget, transform); + LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, renderTarget, transform); } static void android_view_GLES20Canvas_destroyLayer(JNIEnv* env, jobject clazz, Layer* layer) { @@ -795,8 +797,8 @@ static JNINativeMethod gMethods[] = { { "nCreateLayerRenderer", "(I)I", (void*) android_view_GLES20Canvas_createLayerRenderer }, { "nCreateLayer", "(IIZ[I)I", (void*) android_view_GLES20Canvas_createLayer }, { "nResizeLayer", "(III[I)V" , (void*) android_view_GLES20Canvas_resizeLayer }, - { "nCreateTextureLayer", "([I)I", (void*) android_view_GLES20Canvas_createTextureLayer }, - { "nUpdateTextureLayer", "(IIILandroid/graphics/SurfaceTexture;)V", + { "nCreateTextureLayer", "(Z[I)I", (void*) android_view_GLES20Canvas_createTextureLayer }, + { "nUpdateTextureLayer", "(IIIZLandroid/graphics/SurfaceTexture;)V", (void*) android_view_GLES20Canvas_updateTextureLayer }, { "nDestroyLayer", "(I)V", (void*) android_view_GLES20Canvas_destroyLayer }, { "nDestroyLayerDeferred", "(I)V", (void*) android_view_GLES20Canvas_destroyLayerDeferred }, diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png Binary files differindex 94d27cf3df3e..94098902e928 100644 --- a/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png +++ b/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png Binary files differindex 94d27cf3df3e..55acc9ae968d 100644 --- a/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png +++ b/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png diff --git a/core/res/res/drawable-hdpi/ic_notification_ime_default.png b/core/res/res/drawable-hdpi/ic_notification_ime_default.png Binary files differnew file mode 100644 index 000000000000..1a9d88c0b664 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_ime_default.png diff --git a/core/res/res/drawable-xlarge-mdpi/btn_code_lock_default.png b/core/res/res/drawable-large-mdpi/btn_code_lock_default_holo.png Binary files differindex 45cc20dd6ddf..45cc20dd6ddf 100644 --- a/core/res/res/drawable-xlarge-mdpi/btn_code_lock_default.png +++ b/core/res/res/drawable-large-mdpi/btn_code_lock_default_holo.png diff --git a/core/res/res/drawable-xlarge-mdpi/btn_code_lock_touched.png b/core/res/res/drawable-large-mdpi/btn_code_lock_touched_holo.png Binary files differindex 45cc20dd6ddf..45cc20dd6ddf 100644 --- a/core/res/res/drawable-xlarge-mdpi/btn_code_lock_touched.png +++ b/core/res/res/drawable-large-mdpi/btn_code_lock_touched_holo.png diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_drag_direction_green_up.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_drag_direction_green_up.png Binary files differindex 0bc86c36a70a..0bc86c36a70a 100644 --- a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_drag_direction_green_up.png +++ b/core/res/res/drawable-large-mdpi/indicator_code_lock_drag_direction_green_up.png diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_drag_direction_red_up.png Binary files differindex 2ab45477a1b8..2ab45477a1b8 100644 --- a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_drag_direction_red_up.png +++ b/core/res/res/drawable-large-mdpi/indicator_code_lock_drag_direction_red_up.png diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_default_holo.png Binary files differindex fe72d000d4bf..fe72d000d4bf 100644 --- a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_default.png +++ b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_default_holo.png diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_green_holo.png Binary files differindex be666c6f553c..be666c6f553c 100644 --- a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_green.png +++ b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_green_holo.png diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_red.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_red_holo.png Binary files differindex 962719725f26..962719725f26 100644 --- a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_red.png +++ b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_red_holo.png diff --git a/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png Binary files differindex 7d11275a7e92..82fc3b2d331f 100644 --- a/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png +++ b/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png diff --git a/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png Binary files differindex 7d11275a7e92..d1fe1adb6550 100644 --- a/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png +++ b/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png diff --git a/core/res/res/drawable-mdpi/ic_notification_ime_default.png b/core/res/res/drawable-mdpi/ic_notification_ime_default.png Binary files differnew file mode 100644 index 000000000000..1a9d88c0b664 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_ime_default.png diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png Binary files differnew file mode 100644 index 000000000000..f6072516b8c8 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png Binary files differnew file mode 100644 index 000000000000..e057cdd51c59 --- /dev/null +++ b/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png diff --git a/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_default.png b/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_default.png Binary files differnew file mode 100644 index 000000000000..9f7a132dccdf --- /dev/null +++ b/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_default.png diff --git a/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_touched.png b/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_touched.png Binary files differnew file mode 100644 index 000000000000..9f7a132dccdf --- /dev/null +++ b/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_touched.png diff --git a/core/res/res/layout-sw600dp/keyguard_screen_glogin_unlock.xml b/core/res/res/layout-sw600dp/keyguard_screen_glogin_unlock.xml index 97790746decc..56c832cc6ea4 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_glogin_unlock.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_glogin_unlock.xml @@ -26,7 +26,7 @@ android:layout_width="match_parent" android:layout_height="0px" android:layout_weight="1" - android:layout_above="@+id/emergencyCall"> + android:layout_above="@+id/emergencyCallButton"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> @@ -123,7 +123,7 @@ <!-- emergency call button at bottom center --> <Button - android:id="@+id/emergencyCall" + android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml b/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml index 17830882b21f..6579e7633c5b 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml @@ -95,7 +95,7 @@ <!-- emergency call button NOT CURRENTLY USED --> <Button - android:id="@+id/emergencyCall" + android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_emergency" diff --git a/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml b/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml index 63241ddf9e65..c63b0ff1ee7c 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml @@ -87,7 +87,7 @@ <!-- emergency call button --> <Button - android:id="@+id/emergencyCall" + android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawableLeft="@drawable/ic_emergency" @@ -98,4 +98,4 @@ /> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_landscape.xml b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_landscape.xml index b8cbe51ecb97..c65dd83a0387 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_landscape.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_landscape.xml @@ -104,7 +104,7 @@ android:textSize="18sp" /> - <Button android:id="@+id/emergencyCall" + <Button android:id="@+id/emergencyCallButton" android:text="@android:string/lockscreen_emergency_call" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" diff --git a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml index 009148f9c2ea..d8bea5693ade 100644 --- a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml +++ b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml @@ -93,7 +93,7 @@ android:layout_width="match_parent" android:layout_height="1dip" android:layout_marginTop="6dip" - android:layout_above="@id/emergencyCall" + android:layout_above="@id/emergencyCallButton" android:background="@android:drawable/divider_horizontal_dark" /> @@ -107,7 +107,7 @@ <!-- emergency call button --> <Button - android:id="@+id/emergencyCall" + android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawableLeft="@android:drawable/ic_emergency" diff --git a/core/res/res/layout/action_bar_home.xml b/core/res/res/layout/action_bar_home.xml index 7f7c55cb4f68..835e039cc1cd 100644 --- a/core/res/res/layout/action_bar_home.xml +++ b/core/res/res/layout/action_bar_home.xml @@ -25,12 +25,11 @@ android:visibility="gone" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginRight="-4dip" /> + android:layout_marginRight="-8dip" /> <ImageView android:id="@android:id/home" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingLeft="8dip" - android:paddingRight="8dip" + android:layout_marginRight="8dip" android:paddingTop="@dimen/action_bar_icon_vertical_padding" android:paddingBottom="@dimen/action_bar_icon_vertical_padding" android:layout_gravity="center" diff --git a/core/res/res/layout/keyguard_screen_glogin_unlock.xml b/core/res/res/layout/keyguard_screen_glogin_unlock.xml index 8a46546dee58..0e5fe78c5209 100644 --- a/core/res/res/layout/keyguard_screen_glogin_unlock.xml +++ b/core/res/res/layout/keyguard_screen_glogin_unlock.xml @@ -26,7 +26,7 @@ android:layout_width="match_parent" android:layout_height="0px" android:layout_weight="1" - android:layout_above="@+id/emergencyCall"> + android:layout_above="@+id/emergencyCallButton"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" @@ -114,7 +114,7 @@ <!-- emergency call button at bottom center --> <Button - android:id="@+id/emergencyCall" + android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml index aac085371adf..937d5dcd4741 100644 --- a/core/res/res/layout/keyguard_screen_password_landscape.xml +++ b/core/res/res/layout/keyguard_screen_password_landscape.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <!-- ** -** Copyright 2008, The Android Open Source Project +** Copyright 2009, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License") ** you may not use this file except in compliance with the License. @@ -17,89 +17,170 @@ */ --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<!-- This is the general lock screen which shows information about the + state of the device, as well as instructions on how to get past it + depending on the state of the device.--> +<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical"> + android:orientation="vertical" + android:rowCount="10" + android:id="@+id/root" + android:clipChildren="false"> - <View - android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1" - /> + <!-- Column 0 --> + <Space android:height="20dip"/> - <RelativeLayout - android:layout_width="match_parent" - android:layout_height="wrap_content"> + <com.android.internal.widget.DigitalClock android:id="@+id/time" + android:layout_marginTop="16dip" + android:layout_marginBottom="8dip"> - <!-- left side: status --> - <include layout="@layout/keyguard_screen_status_land" + <!-- Because we can't have multi-tone fonts, we render two TextViews, one on + top of the other. Hence the redundant layout... --> + <TextView android:id="@+id/timeDisplayBackground" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_alignParentLeft="true"/> + android:singleLine="true" + android:ellipsize="none" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_marginBottom="6dip" + android:textColor="@color/lockscreen_clock_background" + /> - <!-- right side: password --> - <LinearLayout - android:layout_width="300dip" + <TextView android:id="@+id/timeDisplayForeground" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_alignParentRight="true" - android:layout_centerVertical="true"> - - <!-- Password entry field --> - <EditText android:id="@+id/passwordEntry" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:singleLine="true" - android:textStyle="normal" - android:inputType="textPassword" - android:gravity="center" - android:textSize="24sp" - android:textAppearance="?android:attr/textAppearanceMedium" - android:background="@drawable/lockscreen_password_field_dark" - android:textColor="#ffffffff" - /> - - <!-- Numeric keyboard --> - <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard" - android:layout_width="300dip" - android:layout_height="400dip" - android:background="#40000000" - android:layout_marginTop="5dip" - android:keyBackground="@drawable/btn_keyboard_key_fulltrans" - android:visibility="gone" + android:singleLine="true" + android:ellipsize="none" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" + android:textAppearance="?android:attr/textAppearanceMedium" + android:layout_marginBottom="6dip" + android:textColor="@color/lockscreen_clock_foreground" + android:layout_alignLeft="@id/timeDisplayBackground" + android:layout_alignTop="@id/timeDisplayBackground" /> - </LinearLayout> - </RelativeLayout> + </com.android.internal.widget.DigitalClock> - <View - android:layout_width="match_parent" - android:layout_height="0dip" - android:layout_weight="1" - /> + <TextView + android:id="@+id/date" + android:layout_below="@id/time" + android:layout_marginTop="6dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:layout_gravity="left" + /> - <!-- Alphanumeric keyboard --> - <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboardAlpha" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/password_keyboard_background_holo" - android:keyBackground="@drawable/btn_keyboard_key_fulltrans" - android:keyTextSize="18dip" - android:visibility="gone" - /> + <TextView + android:id="@+id/alarm_status" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:drawablePadding="4dip" + android:layout_marginTop="4dip" + android:layout_gravity="left" + /> + + <TextView + android:id="@+id/status1" + android:layout_marginTop="4dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:drawablePadding="4dip" + android:layout_gravity="left" + /> + + <TextView + android:id="@+id/status2" + android:layout_marginTop="4dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:drawablePadding="4dip" + android:layout_gravity="left" + /> + + <TextView + android:id="@+id/screenLocked" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:gravity="center" + android:layout_marginTop="4dip" + android:drawablePadding="4dip" + android:layout_gravity="left" + /> + + <Space android:height="20dip"/> - <!-- emergency call button NOT CURRENTLY USED --> - <Button - android:id="@+id/emergencyCall" - android:layout_width="wrap_content" + <LinearLayout android:orientation="vertical" > + + <TextView + android:id="@+id/carrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:singleLine="true" + android:ellipsize="marquee" + android:layout_gravity="bottom|left" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" + /> + + <!-- "emergency calls only" shown when sim is missing or PUKd --> + <TextView + android:id="@+id/emergencyCallText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_marginTop="20dip" + android:singleLine="true" + android:ellipsize="marquee" + android:text="@string/emergency_calls_only" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" + /> + + <Button + android:id="@+id/emergencyCallButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:drawableLeft="@drawable/ic_emergency" + android:text="@string/lockscreen_emergency_call" + style="@style/Widget.Button.Transparent" + android:drawablePadding="8dip" + android:layout_marginRight="80dip" + android:visibility="visible" + /> + </LinearLayout> + + <Space android:height="20dip"/> + + <!-- Column 1 --> + <Space android:width="20dip" android:layout_columnWeight="1" android:layout_rowSpan="10" /> + + <!-- Column 2 - password entry field and PIN keyboard --> + <EditText android:id="@+id/passwordEntry" android:layout_height="wrap_content" - android:drawableLeft="@drawable/ic_emergency" - android:drawablePadding="8dip" - android:text="@string/lockscreen_emergency_call" - android:visibility="gone" - style="@style/Widget.Button.Transparent" + android:layout_width="match_parent" + android:singleLine="true" + android:textStyle="normal" + android:inputType="textPassword" + android:gravity="center" + android:textSize="24sp" + android:textAppearance="?android:attr/textAppearanceMedium" + android:background="@drawable/lockscreen_password_field_dark" + android:textColor="?android:attr/textColorPrimary" + /> + + <!-- Numeric keyboard --> + <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard" + android:layout_width="300dip" + android:layout_height="300dip" + android:background="#40000000" + android:layout_marginTop="5dip" + android:keyBackground="@drawable/btn_keyboard_key_fulltrans" + android:visibility="visible" /> -</LinearLayout> +</GridLayout> diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml index 7805672ba61c..30747bc72573 100644 --- a/core/res/res/layout/keyguard_screen_password_portrait.xml +++ b/core/res/res/layout/keyguard_screen_password_portrait.xml @@ -24,23 +24,26 @@ <!-- top: status --> <RelativeLayout android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:layout_marginRight="16dip"> <include layout="@layout/keyguard_screen_status_port" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" - android:layout_alignParentLeft="true"/> + android:layout_alignParentRight="true"/> </RelativeLayout> <!-- emergency call button --> <Button - android:id="@+id/emergencyCall" + android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="4dip" + android:layout_marginRight="16dip" + android:layout_gravity="right" android:drawableLeft="@drawable/ic_emergency" android:drawablePadding="8dip" android:text="@string/lockscreen_emergency_call" - android:visibility="gone" style="@style/Widget.Button.Transparent" /> @@ -55,6 +58,8 @@ android:inputType="textPassword" android:gravity="center" android:textSize="22sp" + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" android:background="@drawable/lockscreen_password_field_dark" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="#ffffffff"/> @@ -83,4 +88,17 @@ android:visibility="gone" /> -</LinearLayout>
\ No newline at end of file + <TextView + android:id="@+id/carrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginBottom="8dip" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:drawablePadding="4dip" + android:singleLine="true" + android:ellipsize="marquee" + /> + +</LinearLayout> diff --git a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml index 244afbe0ebd8..dff2a3fdb9a7 100644 --- a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml +++ b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml @@ -102,7 +102,7 @@ android:textSize="18sp" /> - <Button android:id="@+id/emergencyCall" + <Button android:id="@+id/emergencyCallButton" android:text="@android:string/lockscreen_emergency_call" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" diff --git a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml index 009148f9c2ea..d8bea5693ade 100644 --- a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml +++ b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml @@ -93,7 +93,7 @@ android:layout_width="match_parent" android:layout_height="1dip" android:layout_marginTop="6dip" - android:layout_above="@id/emergencyCall" + android:layout_above="@id/emergencyCallButton" android:background="@android:drawable/divider_horizontal_dark" /> @@ -107,7 +107,7 @@ <!-- emergency call button --> <Button - android:id="@+id/emergencyCall" + android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawableLeft="@android:drawable/ic_emergency" diff --git a/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml b/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml index d58fb232e310..11a6e12bd1f4 100644 --- a/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml +++ b/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml @@ -164,7 +164,7 @@ android:textSize="18sp" /> - <Button android:id="@+id/emergencyCall" + <Button android:id="@+id/emergencyCallButton" android:text="@android:string/lockscreen_emergency_call" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" diff --git a/core/res/res/layout/keyguard_screen_sim_puk_portrait.xml b/core/res/res/layout/keyguard_screen_sim_puk_portrait.xml index 5e392efe0862..e5e045956f56 100644 --- a/core/res/res/layout/keyguard_screen_sim_puk_portrait.xml +++ b/core/res/res/layout/keyguard_screen_sim_puk_portrait.xml @@ -159,7 +159,7 @@ android:layout_width="match_parent" android:layout_height="1dip" android:layout_marginTop="6dip" - android:layout_above="@id/emergencyCall" + android:layout_above="@id/emergencyCallButton" android:background="@android:drawable/divider_horizontal_dark" /> @@ -173,7 +173,7 @@ <!-- emergency call button --> <Button - android:id="@+id/emergencyCall" + android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:drawableLeft="@android:drawable/ic_emergency" diff --git a/core/res/res/layout/keyguard_screen_status_land.xml b/core/res/res/layout/keyguard_screen_status_land.xml index 8a02e1f206b7..60dd9ffc9201 100644 --- a/core/res/res/layout/keyguard_screen_status_land.xml +++ b/core/res/res/layout/keyguard_screen_status_land.xml @@ -26,17 +26,6 @@ android:gravity="left" > - <TextView - android:id="@+id/carrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="17sp" - android:drawablePadding="4dip" - android:singleLine="true" - android:ellipsize="marquee" - /> - <com.android.internal.widget.DigitalClock android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -52,7 +41,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="40sp" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@color/lockscreen_clock_background" android:layout_marginBottom="6dip" @@ -63,7 +52,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="40sp" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@color/lockscreen_clock_foreground" android:layout_alignLeft="@id/timeDisplayBackground" @@ -71,19 +60,6 @@ android:layout_marginBottom="6dip" /> - <TextView android:id="@+id/am_pm" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toRightOf="@id/timeDisplayBackground" - android:layout_alignBaseline="@id/timeDisplayBackground" - android:singleLine="true" - android:ellipsize="none" - android:textSize="18sp" - android:layout_marginLeft="8dip" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textColor="@color/lockscreen_clock_am_pm" - /> - </com.android.internal.widget.DigitalClock> <LinearLayout @@ -98,7 +74,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="17sp"/> + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/> <TextView android:id="@+id/alarm_status" @@ -106,7 +82,7 @@ android:layout_height="wrap_content" android:layout_marginLeft="30dip" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="17sp"/> + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/> </LinearLayout> @@ -117,7 +93,7 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="17sp" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_marginTop="10dip" android:drawablePadding="4dip" android:visibility="gone" @@ -129,7 +105,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dip" - android:textSize="17sp" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:textAppearance="?android:attr/textAppearanceMedium" /> diff --git a/core/res/res/layout/keyguard_screen_status_port.xml b/core/res/res/layout/keyguard_screen_status_port.xml index 1e87fb396614..e72df5b63a53 100644 --- a/core/res/res/layout/keyguard_screen_status_port.xml +++ b/core/res/res/layout/keyguard_screen_status_port.xml @@ -25,23 +25,10 @@ android:layout_height="wrap_content" android:gravity="left"> - <TextView - android:id="@+id/carrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="17sp" - android:drawablePadding="4dip" - android:layout_marginTop="10dip" - android:singleLine="true" - android:ellipsize="marquee" - /> - <com.android.internal.widget.DigitalClock android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="8dip" - android:layout_marginBottom="8dip"> + android:layout_marginTop="8dip"> <!-- Because we can't have multi-tone fonts, we render two TextViews, one on top of the other. Hence the redundant layout... --> @@ -50,7 +37,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="60sp" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@color/lockscreen_clock_background" android:layout_marginBottom="6dip" @@ -61,7 +48,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="60sp" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="@color/lockscreen_clock_foreground" android:layout_marginBottom="6dip" @@ -69,19 +56,6 @@ android:layout_alignTop="@id/timeDisplayBackground" /> - <TextView android:id="@+id/am_pm" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toRightOf="@id/timeDisplayBackground" - android:layout_alignBaseline="@id/timeDisplayBackground" - android:singleLine="true" - android:ellipsize="none" - android:textSize="22sp" - android:layout_marginLeft="8dip" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textColor="@color/lockscreen_clock_am_pm" - /> - </com.android.internal.widget.DigitalClock> <LinearLayout @@ -89,45 +63,50 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/time" - android:layout_marginTop="10dip"> + android:layout_marginTop="16dip" + android:layout_gravity="right"> <TextView android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="17sp"/> + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/> <TextView android:id="@+id/alarm_status" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="30dip" + android:layout_marginLeft="16dip" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="17sp"/> + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:drawablePadding="4dip"/> </LinearLayout> - <!-- used for status such as the next alarm, and charging status. --> <TextView - android:id="@+id/status2" + android:id="@+id/status1" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentTop="true" + android:layout_gravity="right" + android:layout_marginTop="4dip" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="17sp" - android:layout_marginTop="10dip" - android:drawablePadding="4dip" - android:visibility="gone" /> + <!-- used for status such as the next alarm, and charging status. --> <TextView - android:id="@+id/status1" + android:id="@+id/status2" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="10dip" - android:textSize="17sp" + android:layout_gravity="right" + android:layout_alignParentTop="true" android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:layout_marginTop="4dip" + android:drawablePadding="4dip" + android:visibility="gone" /> + </LinearLayout> diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml index 7ae357a86006..020bb27742ed 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml @@ -28,43 +28,14 @@ android:id="@+id/root" android:clipChildren="false"> - <TextView - android:id="@+id/carrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:layout_alignParentRight="true" - android:layout_marginTop="10dip" - android:layout_marginRight="8dip" - android:singleLine="true" - android:ellipsize="marquee" - android:gravity="right|bottom" - android:textAppearance="?android:attr/textAppearanceMedium" - /> - - <!-- "emergency calls only" shown when sim is missing or PUKd --> - <TextView - android:id="@+id/emergencyCallText" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/carrier" - android:layout_alignParentRight="true" - android:layout_marginTop="0dip" - android:layout_marginRight="8dip" - android:text="@string/emergency_calls_only" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="@color/white" - /> - <!-- time and date --> <com.android.internal.widget.DigitalClock android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@id/carrier" - android:layout_marginTop="52dip" - android:layout_marginLeft="20dip" - android:layout_marginBottom="8dip" - > + android:layout_alignParentRight="true" + android:layout_marginRight="16dip" + android:layout_marginTop="48dip" + android:layout_marginLeft="20dip"> <!-- Because we can't have multi-tone fonts, we render two TextViews, one on top of the other. Hence the redundant layout... --> @@ -73,7 +44,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="72sp" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginBottom="6dip" android:textColor="@color/lockscreen_clock_background" @@ -84,7 +55,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="72sp" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginBottom="6dip" android:textColor="@color/lockscreen_clock_foreground" @@ -92,51 +63,50 @@ android:layout_alignTop="@id/timeDisplayBackground" /> - <TextView android:id="@+id/am_pm" + </com.android.internal.widget.DigitalClock> + + <LinearLayout android:id="@+id/date_alarm_status_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/time" + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:layout_marginTop="16dip" + android:layout_alignParentRight="true" + android:gravity="right" + android:orientation="horizontal"> + + <TextView + android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_toRightOf="@id/timeDisplayBackground" - android:layout_alignBaseline="@id/timeDisplayBackground" - android:singleLine="true" - android:ellipsize="none" - android:textSize="22sp" - android:layout_marginLeft="8dip" android:textAppearance="?android:attr/textAppearanceMedium" - android:textColor="@color/lockscreen_clock_am_pm" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" /> - </com.android.internal.widget.DigitalClock> + <TextView + android:id="@+id/alarm_status" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:drawablePadding="4dip" + android:layout_marginLeft="16dip" + /> - <TextView - android:id="@+id/date" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@id/time" - android:layout_marginLeft="24dip" - android:textAppearance="?android:attr/textAppearanceMedium" - /> - - <!-- TODO: Redo layout when we release on phones --> - <TextView - android:id="@+id/alarm_status" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="18sp" - android:drawablePadding="4dip" - android:layout_below="@id/date" - android:layout_marginTop="4dip" - android:layout_marginLeft="24dip" - /> + </LinearLayout> <TextView android:id="@+id/status1" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@id/alarm_status" + android:layout_below="@id/date_alarm_status_container" android:layout_marginTop="4dip" - android:layout_marginLeft="24dip" + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:layout_alignParentRight="true" android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" /> @@ -146,9 +116,13 @@ android:layout_height="wrap_content" android:layout_below="@id/status1" android:layout_marginTop="4dip" - android:layout_marginLeft="24dip" + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:layout_alignParentRight="true" android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" + android:visibility="gone" /> <TextView @@ -156,46 +130,89 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/status2" - android:layout_marginLeft="24dip" + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:layout_alignParentRight="true" android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_marginTop="12dip" android:drawablePadding="4dip" + android:visibility="gone" /> - <com.android.internal.widget.multiwaveview.MultiWaveView - android:id="@+id/unlock_widget" - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="300dip" - android:layout_alignParentBottom="true" - - android:targetDrawables="@array/lockscreen_targets_when_silent" - android:handleDrawable="@drawable/ic_lockscreen_handle" - android:waveDrawable="@drawable/ic_lockscreen_outerring" - android:outerRadius="@*android:dimen/multiwaveview_target_placement_radius" - android:snapMargin="@*android:dimen/multiwaveview_snap_margin" - android:hitRadius="@*android:dimen/multiwaveview_hit_radius" - android:vibrationDuration="20" - android:rightChevronDrawable="@drawable/ic_lockscreen_chevron_right" - android:feedbackCount="3" - android:horizontalOffset="0dip" - android:verticalOffset="60dip" - /> - - <!-- emergency call button shown when sim is PUKd and tab_selector is - hidden --> + <!-- emergency call button shown when sim is PUKd and tab_selector is hidden --> <Button android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="4dip" + android:layout_marginRight="16dip" android:drawableLeft="@drawable/ic_emergency" - android:layout_centerInParent="true" - android:layout_alignParentBottom="true" - android:layout_marginBottom="80dip" + android:layout_alignParentRight="true" + android:layout_below="@id/screenLocked" style="@style/Widget.Button.Transparent" - android:drawablePadding="8dip" - android:visibility="gone" + android:drawablePadding="4dip" + android:text="@string/lockscreen_emergency_call" + android:visibility="visible" + android:background="@null" /> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:gravity="center_horizontal"> + + <com.android.internal.widget.multiwaveview.MultiWaveView + android:id="@+id/unlock_widget" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="300dip" + android:layout_alignParentBottom="true" + + android:targetDrawables="@array/lockscreen_targets_when_silent" + android:handleDrawable="@drawable/ic_lockscreen_handle" + android:waveDrawable="@drawable/ic_lockscreen_outerring" + android:outerRadius="@dimen/multiwaveview_target_placement_radius" + android:snapMargin="@dimen/multiwaveview_snap_margin" + android:hitRadius="@dimen/multiwaveview_hit_radius" + android:rightChevronDrawable="@drawable/ic_lockscreen_chevron_right" + android:horizontalOffset="0dip" + android:verticalOffset="60dip" + android:feedbackCount="3" + android:vibrationDuration="20" + /> + + <TextView + android:id="@+id/carrier" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_marginBottom="8dip" + android:gravity="center_horizontal" + android:singleLine="true" + android:ellipsize="marquee" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" + /> + + <!-- "emergency calls only" shown when sim is missing or PUKd --> + <TextView + android:id="@+id/emergencyCallText" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_above="@id/carrier" + android:gravity="center_horizontal" + android:layout_marginTop="0dip" + android:layout_marginRight="8dip" + android:text="@string/emergency_calls_only" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" + /> + + </RelativeLayout> + </RelativeLayout> diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml index d45cc859968f..930ac332f1bb 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml @@ -32,8 +32,9 @@ <Space android:height="20dip"/> <com.android.internal.widget.DigitalClock android:id="@+id/time" - android:layout_marginTop="56dip" - android:layout_marginBottom="8dip"> + android:layout_marginTop="16dip" + android:layout_marginBottom="8dip" + android:layout_gravity="right"> <!-- Because we can't have multi-tone fonts, we render two TextViews, one on top of the other. Hence the redundant layout... --> @@ -42,7 +43,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="72sp" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginBottom="6dip" android:textColor="@color/lockscreen_clock_background" @@ -53,7 +54,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="72sp" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginBottom="6dip" android:textColor="@color/lockscreen_clock_foreground" @@ -61,20 +62,6 @@ android:layout_alignTop="@id/timeDisplayBackground" /> - <TextView android:id="@+id/am_pm" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toRightOf="@id/timeDisplayBackground" - android:layout_alignBaseline="@id/timeDisplayBackground" - android:singleLine="true" - android:ellipsize="none" - android:textSize="22sp" - android:layout_marginLeft="8dip" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textColor="@color/lockscreen_clock_am_pm" - android:visibility="gone" - /> - </com.android.internal.widget.DigitalClock> <TextView @@ -82,46 +69,51 @@ android:layout_below="@id/time" android:layout_marginTop="6dip" android:textAppearance="?android:attr/textAppearanceMedium" - android:layout_gravity="left" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:layout_gravity="right" /> <TextView android:id="@+id/alarm_status" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="18sp" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" android:layout_marginTop="4dip" - android:layout_gravity="left" + android:layout_gravity="right" /> <TextView android:id="@+id/status1" android:layout_marginTop="4dip" android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" - android:layout_gravity="left" + android:layout_gravity="right" /> <TextView android:id="@+id/status2" android:layout_marginTop="4dip" android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" - android:layout_gravity="left" + android:layout_gravity="right" /> <TextView android:id="@+id/screenLocked" android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:gravity="center" android:layout_marginTop="4dip" android:drawablePadding="4dip" - android:layout_gravity="left" + android:layout_gravity="right" /> <Space android:height="20dip"/> - <LinearLayout android:orientation="horizontal" > + <LinearLayout android:orientation="vertical" + android:gravity="fill"> <TextView android:id="@+id/carrier" @@ -130,19 +122,10 @@ android:layout_alignParentTop="true" android:singleLine="true" android:ellipsize="marquee" - android:layout_gravity="bottom|left" + android:layout_gravity="right" android:textAppearance="?android:attr/textAppearanceMedium" - /> - - <Button - android:id="@+id/emergencyCallButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:drawableLeft="@drawable/ic_emergency" - style="@style/Widget.Button.Transparent" - android:drawablePadding="8dip" - android:layout_marginRight="80dip" - android:visibility="gone" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" /> <!-- "emergency calls only" shown when sim is missing or PUKd --> @@ -152,13 +135,28 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginTop="20dip" + android:singleLine="true" + android:ellipsize="marquee" android:text="@string/emergency_calls_only" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="@color/white" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:textColor="?android:attr/textColorSecondary" + android:layout_gravity="right" /> - </LinearLayout> - <Space android:height="20dip"/> + <Button + android:id="@+id/emergencyCallButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:drawableLeft="@drawable/ic_emergency" + android:text="@string/lockscreen_emergency_call" + style="@style/Widget.Button.Transparent" + android:drawablePadding="8dip" + android:layout_marginRight="80dip" + android:visibility="visible" + android:layout_gravity="right" + /> + </LinearLayout> <!-- Column 1 --> <Space android:width="20dip" android:layout_columnWeight="1" android:layout_rowSpan="10" /> @@ -173,12 +171,12 @@ android:targetDrawables="@array/lockscreen_targets_when_silent" android:handleDrawable="@drawable/ic_lockscreen_handle" android:waveDrawable="@drawable/ic_lockscreen_outerring" - android:outerRadius="@*android:dimen/multiwaveview_target_placement_radius" - android:snapMargin="@*android:dimen/multiwaveview_snap_margin" - android:hitRadius="@*android:dimen/multiwaveview_hit_radius" - android:vibrationDuration="20" + android:outerRadius="@dimen/multiwaveview_target_placement_radius" + android:snapMargin="@dimen/multiwaveview_snap_margin" + android:hitRadius="@dimen/multiwaveview_hit_radius" android:topChevronDrawable="@drawable/ic_lockscreen_chevron_up" android:feedbackCount="3" + android:vibrationDuration="20" android:horizontalOffset="0dip" android:verticalOffset="0dip" /> diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml index d52bc57c8c59..8319b8a6d6e6 100644 --- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml +++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml @@ -26,7 +26,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - android:rowCount="9"> + android:rowCount="8"> <!-- Column 0: Time, date and status --> <com.android.internal.widget.DigitalClock android:id="@+id/time" @@ -44,7 +44,7 @@ android:singleLine="true" android:ellipsize="none" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_pattern_unlock_clock_font_size" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:layout_marginBottom="6dip" android:textColor="@color/lockscreen_clock_background" /> @@ -55,7 +55,7 @@ android:singleLine="true" android:ellipsize="none" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_pattern_unlock_clock_font_size" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:layout_marginBottom="6dip" android:layout_alignLeft="@id/timeDisplayBackground" android:layout_alignTop="@id/timeDisplayBackground" @@ -69,7 +69,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_gravity="right" /> @@ -78,7 +78,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_gravity="right" android:drawablePadding="4dip" /> @@ -88,47 +88,42 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:layout_gravity="right" /> - <TextView - android:id="@+id/status2" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" - android:layout_gravity="right" - android:drawablePadding="4dip" - android:visibility="gone" - /> - - <Space android:layout_rowWeight="1" android:layout_columnWeight="1" /> + <!-- TODO: remove hard coded height since layout_rowWeight doesn't seem to be working --> + <Space + android:layout_height="43dip" + android:layout_gravity="fill" + android:layout_rowWeight="1" android:layout_columnWeight="1" /> <TextView android:id="@+id/carrier" android:layout_gravity="right" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:singleLine="true" android:ellipsize="marquee" /> <Button android:id="@+id/emergencyCallButton" android:layout_gravity="right" - style="@*android:style/Widget.Button.Transparent" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" + style="@style/Widget.Button.Transparent" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:text="@string/lockscreen_emergency_call" - android:drawableLeft="@*android:drawable/lockscreen_emergency_button" + android:drawableLeft="@drawable/lockscreen_emergency_button" android:drawablePadding="0dip" + android:background="@null" /> <Button android:id="@+id/forgotPatternButton" android:layout_gravity="right" - style="@*android:style/Widget.Button.Transparent" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" - android:text="@*android:string/lockscreen_forgot_pattern_button_text" - android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button" + style="@style/Widget.Button.Transparent" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" + android:text="@string/lockscreen_forgot_pattern_button_text" + android:drawableLeft="@drawable/lockscreen_forgot_password_button" android:drawablePadding="0dip" + android:background="@null" /> <!-- Column 1: lock pattern --> @@ -139,6 +134,6 @@ android:layout_marginRight="8dip" android:layout_marginBottom="8dip" android:layout_marginLeft="8dip" - android:layout_rowSpan="9"/> + android:layout_rowSpan="8"/> </GridLayout> diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml index dd68d82f6269..bbe2006e47d8 100644 --- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml +++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml @@ -30,27 +30,28 @@ <com.android.internal.widget.DigitalClock android:id="@+id/time" android:layout_marginBottom="18dip" + android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin" android:layout_gravity="right"> <!-- Because we can't have multi-tone fonts, we render two TextViews, one on top of the other. Hence the redundant layout... --> - <TextView android:id="@*android:id/timeDisplayBackground" + <TextView android:id="@+id/timeDisplayBackground" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="@*android:dimen/keyguard_pattern_unlock_clock_font_size" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginBottom="6dip" - android:textColor="@*android:color/lockscreen_clock_background" + android:textColor="@color/lockscreen_clock_background" /> - <TextView android:id="@*android:id/timeDisplayForeground" + <TextView android:id="@+id/timeDisplayForeground" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="none" - android:textSize="@*android:dimen/keyguard_pattern_unlock_clock_font_size" + android:textSize="@dimen/keyguard_lockscreen_clock_font_size" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_marginBottom="6dip" android:textColor="@color/lockscreen_clock_foreground" @@ -60,14 +61,15 @@ <LinearLayout android:orientation="horizontal" - android:layout_gravity="right"> + android:layout_gravity="right" + android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin"> <TextView android:id="@+id/date" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" /> <TextView @@ -76,7 +78,7 @@ android:layout_height="wrap_content" android:layout_marginLeft="16dip" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" /> @@ -85,22 +87,31 @@ <TextView android:id="@+id/status1" + android:layout_gravity="right" + android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" - android:layout_gravity="right" + android:singleLine="true" + android:ellipsize="marquee" /> <TextView android:id="@+id/status2" - android:layout_alignParentTop="true" + android:layout_gravity="right" + android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:drawablePadding="4dip" - android:layout_gravity="right" + android:singleLine="true" + android:ellipsize="marquee" android:visibility="gone" /> + <!-- We need MATCH_PARENT here only to force the size of the parent to be passed to + the pattern view for it to compute its size. This is an unusual case, caused by + LockPatternView's requirement to maintain a square aspect ratio based on the width + of the screen. --> <com.android.internal.widget.LockPatternView android:id="@+id/lockPattern" android:layout_width="match_parent" @@ -109,6 +120,8 @@ android:layout_marginRight="8dip" android:layout_marginBottom="4dip" android:layout_marginLeft="8dip" + android:layout_gravity="center|bottom" + android:layout_rowWeight="1" /> <TextView @@ -116,25 +129,25 @@ android:layout_gravity="center_horizontal" android:singleLine="true" android:ellipsize="marquee" - android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:textAppearance="?android:attr/textAppearanceMedium" /> <!-- Footer: an emergency call button and an initially hidden "Forgot pattern" button --> <LinearLayout android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_gravity="center"> + android:layout_gravity="fill_horizontal"> <Button android:id="@+id/emergencyCallButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" style="@style/Widget.Button.Transparent" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:text="@string/lockscreen_emergency_call" android:drawableLeft="@drawable/lockscreen_emergency_button" android:drawablePadding="0dip" + android:background="@null" /> <Button android:id="@+id/forgotPatternButton" @@ -142,10 +155,11 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal" style="@style/Widget.Button.Transparent" - android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size" + android:textSize="@dimen/keyguard_lockscreen_status_line_font_size" android:text="@string/lockscreen_forgot_pattern_button_text" android:drawableLeft="@drawable/lockscreen_forgot_password_button" android:drawablePadding="0dip" + android:background="@null" /> </LinearLayout> diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml index b8ce9b4bb95a..4dd93346a38d 100644 --- a/core/res/res/values-land/dimens.xml +++ b/core/res/res/values-land/dimens.xml @@ -31,8 +31,10 @@ <!-- Default height of an action bar. --> <dimen name="action_bar_default_height">40dip</dimen> + <!-- Vertical padding around action bar icons. --> + <dimen name="action_bar_icon_vertical_padding">4dip</dimen> - <!-- Size of clock font in LockScreen. --> - <dimen name="keyguard_pattern_unlock_clock_font_size">80sp</dimen> + <!-- Size of clock font in LockScreen on Unsecure unlock screen. --> + <dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen> </resources> diff --git a/core/res/res/values-sw600dp/bools.xml b/core/res/res/values-sw600dp/bools.xml index 734031f2d93b..d73ff9977caa 100644 --- a/core/res/res/values-sw600dp/bools.xml +++ b/core/res/res/values-sw600dp/bools.xml @@ -16,4 +16,5 @@ <resources> <bool name="preferences_prefer_dual_pane">true</bool> + <bool name="show_ongoing_ime_switcher">false</bool> </resources> diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml index 9d6309da0f6d..9647bb707c53 100644 --- a/core/res/res/values/bools.xml +++ b/core/res/res/values/bools.xml @@ -19,4 +19,5 @@ <bool name="action_bar_embed_tabs">false</bool> <bool name="split_action_bar_is_narrow">true</bool> <bool name="preferences_prefer_dual_pane">false</bool> + <bool name="show_ongoing_ime_switcher">true</bool> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 52b00c6af8eb..a8b7b75c7fa8 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -98,7 +98,7 @@ <!-- This string array should be overridden by the device to present a list of network attributes. This is used by the connectivity manager to decide which networks can coexist based on the hardware --> - <!-- An Array of "[Connection name],[ConnectivityManager connection type], + <!-- An Array of "[Connection name],[ConnectivityManager.TYPE_xxxx], [associated radio-type],[priority],[restoral-timer(ms)],[dependencyMet] --> <!-- the 5th element "resore-time" indicates the number of milliseconds to delay before automatically restore the default connection. Set -1 if the connection @@ -154,20 +154,16 @@ <string-array translatable="false" name="config_tether_dhcp_range"> </string-array> - <!-- Regex array of allowable upstream ifaces for tethering - for example if you want - tethering on a new interface called "foo2" add <item>"foo\\d"</item> to the array --> - <!-- Interfaces will be prioritized according to the order listed --> - <string-array translatable="false" name="config_tether_upstream_regexs"> - </string-array> - <!-- Regex of wired ethernet ifaces --> <string translatable="false" name="config_ethernet_iface_regex">eth\\d</string> - <!-- Boolean indicating if we require the use of DUN on mobile for tethering. - Note that this defaults to false so that if you move to a carrier that - hasn't configured anything tethering will still work. If you'd rather - make the device untetherable on unconfigured devices, set to true --> - <bool translatable="false" name="config_tether_dun_required">false</bool> + <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering --> + <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or + <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH --> + <integer-array translatable="false" name="config_tether_upstream_types"> + <item>1</item> + <item>4</item> + </integer-array> <!-- String containing the apn value for tethering. May be overriden by secure settings TETHER_DUN_APN. Value is a comma separated series of strings: diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 20cb85273a8f..9933b6ff376b 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -118,15 +118,15 @@ <!-- Default height of an action bar. --> <dimen name="action_bar_default_height">48dip</dimen> <!-- Vertical padding around action bar icons. --> - <dimen name="action_bar_icon_vertical_padding">4dip</dimen> + <dimen name="action_bar_icon_vertical_padding">8dip</dimen> - <!-- Size of clock font in LockScreen. --> - <dimen name="keyguard_pattern_unlock_clock_font_size">80sp</dimen> + <!-- Size of clock font in LockScreen on Unsecure unlock screen. --> + <dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen> - <!-- Size of status line font in LockScreen. --> - <dimen name="keyguard_pattern_unlock_status_line_font_size">14sp</dimen> + <!-- Size of status line font on Unsecure unlock LockScreen. --> + <dimen name="keyguard_lockscreen_status_line_font_size">14sp</dimen> - <!-- Size of right margin in LockScreen --> - <dimen name="keyguard_pattern_unlock_status_line_font_right_margin">20dip</dimen> + <!-- Size of right margin on Unsecure unlock LockScreen --> + <dimen name="keyguard_lockscreen_status_line_font_right_margin">45dip</dimen> </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 72a579755563..67be63ed15ba 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1118,9 +1118,9 @@ <style name="Widget.ActionButton"> <item name="android:background">?android:attr/selectableItemBackground</item> - <item name="android:paddingLeft">16dip</item> - <item name="android:paddingRight">16dip</item> - <item name="android:minWidth">64dip</item> + <item name="android:paddingLeft">12dip</item> + <item name="android:paddingRight">12dip</item> + <item name="android:minWidth">56dip</item> <item name="android:minHeight">?android:attr/actionBarSize</item> </style> @@ -1788,18 +1788,16 @@ </style> <style name="Widget.Holo.ActionButton" parent="Widget.ActionButton"> - <item name="android:minWidth">64dip</item> + <item name="android:minWidth">56dip</item> <item name="android:gravity">center</item> - <item name="android:paddingLeft">16dip</item> - <item name="android:paddingRight">16dip</item> + <item name="android:paddingLeft">12dip</item> + <item name="android:paddingRight">12dip</item> <item name="android:scaleType">center</item> </style> <style name="Widget.Holo.ActionButton.Overflow"> <item name="android:src">@android:drawable/ic_menu_moreoverflow_holo_dark</item> <item name="android:background">?android:attr/selectableItemBackground</item> - <item name="android:paddingLeft">16dip</item> - <item name="android:paddingRight">16dip</item> <item name="android:contentDescription">@string/action_menu_overflow_description</item> </style> @@ -2142,8 +2140,6 @@ <style name="Widget.Holo.Light.ActionButton.Overflow"> <item name="android:src">@android:drawable/ic_menu_moreoverflow_holo_light</item> - <item name="android:paddingLeft">16dip</item> - <item name="android:paddingRight">16dip</item> <item name="android:contentDescription">@string/action_menu_overflow_description</item> </style> diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index 55d711f7adf2..75b8ead07223 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -270,8 +270,8 @@ </li> <li><a href="<?cs var:toroot ?>guide/topics/media/index.html"> - <span class="en">Audio and Video</span> - </a></li> + <span class="en">Media</span> + </a><span class="new">updated!</span></li> <li> <a href="<?cs var:toroot ?>guide/topics/clipboard/copy-paste.html"> <span class="en">Copy and Paste</span> diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd index e589292842e0..78b5b511b603 100644 --- a/docs/html/guide/topics/appwidgets/index.jd +++ b/docs/html/guide/topics/appwidgets/index.jd @@ -213,7 +213,7 @@ dp and for a width of four cells.</p> <p class="note"><strong>Note:</strong> To make your app widget portable across devices, your app widget's minimum size should never be larger than 4 x 4 cells. See the <a -href="{@docRoot}guide/practices/ui_guidelines/widget_design.htmll#sizes">App +href="{@docRoot}guide/practices/ui_guidelines/widget_design.html#sizes">App Widget Design Guidelines</a> for more discussion of Home screen cell sizes.</p> </li> <li>The <code>updatePeriodMillis</code> attribute defines how often the App diff --git a/docs/html/guide/topics/fundamentals/services.jd b/docs/html/guide/topics/fundamentals/services.jd index 468a417e62cc..d3ef70aaef63 100644 --- a/docs/html/guide/topics/fundamentals/services.jd +++ b/docs/html/guide/topics/fundamentals/services.jd @@ -670,7 +670,7 @@ foreground service must provide a notification for the status bar, which is plac either stopped or removed from the foreground.</p> <p>For example, a music player that plays music from a service should be set to run in the -foreground, because the user it explicitly aware +foreground, because the user is explicitly aware of its operation. The notification in the status bar might indicate the current song and allow the user to launch an activity to interact with the music player.</p> diff --git a/docs/html/guide/topics/media/images/notification1.png b/docs/html/guide/topics/media/images/notification1.png Binary files differnew file mode 100644 index 000000000000..9b0197107b0d --- /dev/null +++ b/docs/html/guide/topics/media/images/notification1.png diff --git a/docs/html/guide/topics/media/images/notification2.png b/docs/html/guide/topics/media/images/notification2.png Binary files differnew file mode 100644 index 000000000000..488648e106f9 --- /dev/null +++ b/docs/html/guide/topics/media/images/notification2.png diff --git a/docs/html/guide/topics/media/index.jd b/docs/html/guide/topics/media/index.jd index b6d162917ca8..06e620815470 100644 --- a/docs/html/guide/topics/media/index.jd +++ b/docs/html/guide/topics/media/index.jd @@ -1,4 +1,4 @@ -page.title=Audio and Video +page.title=Media @jd:body <div id="qv-wrapper"> @@ -6,30 +6,44 @@ page.title=Audio and Video <h2>Quickview</h2> <ul> -<li>Audio playback and record</li> -<li>Video playback</li> -<li>Handles data from raw resources, files, streams</li> -<li>Built-in codecs for a variety of media. See <a href="{@docRoot}guide/appendix/media-formats.html">Android Supported Media Formats</a></li> +<li>MediaPlayer APIs allow you to play and record media</li> +<li>You can handle data from raw resources, files, and streams</li> +<li>The platform supports a variety of media formats. See <a +href="{@docRoot}guide/appendix/media-formats.html">Android Supported Media Formats</a></li> </ul> <h2>In this document</h2> <ol> -<li><a href="#playback">Audio and Video Playback</a> - <ol> - <li><a href="#playraw">Playing from a Raw Resource</li> - <li><a href="#playfile">Playing from a File or Stream</li> - <li><a href="#jet">Playing JET Content</li> - </ol> +<li><a href="#mediaplayer">Using MediaPlayer</a> + <ol> + <li><a href='#preparingasync'>Asynchronous Preparation</a></li> + <li><a href='#managestate'>Managing State</a></li> + <li><a href='#releaseplayer'>Releasing the MediaPlayer</a></li> + </ol> </li> -<li><a href="#capture">Audio Capture</a></li> +<li><a href="#mpandservices">Using a Service with MediaPlayer</a> + <ol> + <li><a href="#asyncprepare">Running asynchronously</a></li> + <li><a href="#asyncerror">Handling asynchronous errors</a></li> + <li><a href="#wakelocks">Using wake locks</a></li> + <li><a href="#foregroundserv">Running as a foreground service</a></li> + <li><a href="#audiofocus">Handling audio focus</a></li> + <li><a href="#cleanup">Performing cleanup</a></li> + </ol> +</li> +<li><a href="#noisyintent">Handling the AUDIO_BECOMING_NOISY Intent</a> +<li><a href="#viacontentresolver">Retrieving Media from a Content Resolver</a> +<li><a href="#jetcontent">Playing JET content</a> +<li><a href="#audiocapture">Performing Audio Capture</a> </ol> <h2>Key classes</h2> <ol> -<li>{@link android.media.MediaPlayer MediaPlayer}</li> -<li>{@link android.media.MediaRecorder MediaRecorder}</li> -<li>{@link android.media.JetPlayer JetPlayer}</li> -<li>{@link android.media.SoundPool SoundPool}</li> +<li>{@link android.media.MediaPlayer}</li> +<li>{@link android.media.MediaRecorder}</li> +<li>{@link android.media.AudioManager}</li> +<li>{@link android.media.JetPlayer}</li> +<li>{@link android.media.SoundPool}</li> </ol> <h2>See also</h2> @@ -41,129 +55,729 @@ page.title=Audio and Video </div> </div> -<p>The Android platform offers built-in encoding/decoding for a variety of -common media types, so that you can easily integrate audio, video, and images into your -applications. Accessing the platform's media capabilities is fairly straightforward -— you do so using the same intents and activities mechanism that the rest of -Android uses.</p> - -<p>Android lets you play audio and video from several types of data sources. You -can play audio or video from media files stored in the application's resources -(raw resources), from standalone files in the filesystem, or from a data stream -arriving over a network connection. To play audio or video from your -application, use the {@link android.media.MediaPlayer} class.</p> - -<p>The platform also lets you record audio and video, where supported by the -mobile device hardware. To record audio or video, use the {@link -android.media.MediaRecorder} class. Note that the emulator doesn't have hardware -to capture audio or video, but actual mobile devices are likely to provide these -capabilities, accessible through the MediaRecorder class. </p> - -<p>For a list of media formats for which Android offers built-in support, -see the <a href="{@docRoot}guide/appendix/media-formats.html">Android Media -Formats</a> appendix. </p> - -<h2 id="playback">Audio and Video Playback</h2> -<p>Media can be played from anywhere: from a raw resource, from a file from the system, -or from an available network (URL).</p> - -<p>You can play back the audio data only to the standard -output device; currently, that is the mobile device speaker or Bluetooth headset. You -cannot play sound files in the conversation audio. </p> - -<h3 id="playraw">Playing from a Raw Resource</h3> -<p>Perhaps the most common thing to want to do is play back media (notably sound) -within your own applications. Doing this is easy:</p> -<ol> - <li>Put the sound (or other media resource) file into the <code>res/raw</code> - folder of your project, where the Eclipse plugin (or aapt) will find it and - make it into a resource that can be referenced from your R class</li> - <li>Create an instance of <code>MediaPlayer</code>, referencing that resource using - {@link android.media.MediaPlayer#create MediaPlayer.create}, and then call - {@link android.media.MediaPlayer#start() start()} on the instance:</li> -</ol> +<p>The Android multimedia framework includes support for encoding and decoding a +variety of common media types, so that you can easily integrate audio, +video and images into your applications. You can play audio or video from media files stored in your +application's resources (raw resources), from standalone files in the filesystem, or from a data +stream arriving over a network connection, all using {@link android.media.MediaPlayer} APIs.</p> + +<p>You can also record audio and video using the {@link android.media.MediaRecorder} APIs if +supported by the device hardware. Note that the emulator doesn't have hardware to capture audio or +video, but actual mobile devices are likely to provide these capabilities.</p> + +<p>This document shows you how to write a media-playing application that interacts with the user and +the system in order to obtain good performance and a pleasant user experience.</p> + +<p class="note"><strong>Note:</strong> You can play back the audio data only to the standard output +device. Currently, that is the mobile device speaker or a Bluetooth headset. You cannot play sound +files in the conversation audio during a call.</p> + + +<h2 id="mediaplayer">Using MediaPlayer</h2> + +<p>One of the most important components of the media framework is the +{@link android.media.MediaPlayer MediaPlayer} +class. An object of this class can fetch, decode, and play both audio and video +with minimal setup. It supports several different media sources such as: +<ul> + <li>Local resources</li> + <li>Internal URIs, such as one you might obtain from a Content Resolver</li> + <li>External URLs (streaming)</li> +</ul> +</p> + +<p>For a list of media formats that Android supports, +see the <a href="{@docRoot}guide/appendix/media-formats.html">Android Supported Media +Formats</a> document. </p> + +<p>Here is an example +of how to play audio that's available as a local raw resource (saved in your application's +{@code res/raw/} directory):</p> + +<pre>MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1); +mediaPlayer.start(); // no need to call prepare(); create() does that for you +</pre> + +<p>In this case, a "raw" resource is a file that the system does not +try to parse in any particular way. However, the content of this resource should not +be raw audio. It should be a properly encoded and formatted media file in one +of the supported formats.</p> + +<p>And here is how you might play from a URI available locally in the system +(that you obtained through a Content Resolver, for instance):</p> + +<pre>Uri myUri = ....; // initialize Uri here +MediaPlayer mediaPlayer = new MediaPlayer(); +mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); +mediaPlayer.setDataSource(getApplicationContext(), myUri); +mediaPlayer.prepare(); +mediaPlayer.start();</pre> + +<p>Playing from a remote URL via HTTP streaming looks like this:</p> + +<pre>String url = "http://........"; // your URL here +MediaPlayer mediaPlayer = new MediaPlayer(); +mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); +mediaPlayer.setDataSource(url); +mediaPlayer.prepare(); // might take long! (for buffering, etc) +mediaPlayer.start();</pre> + +<p class="note"><strong>Note:</strong> +If you're passing a URL to stream an online media file, the file must be capable of +progressive download.</p> + +<p class="caution"><strong>Caution:</strong> You must either catch or pass +{@link java.lang.IllegalArgumentException} and {@link java.io.IOException} when using +{@link android.media.MediaPlayer#setDataSource setDataSource()}, because +the file you are referencing might not exist.</p> + +<h3 id='#preparingasync'>Asynchronous Preparation</h3> + +<p>Using {@link android.media.MediaPlayer MediaPlayer} can be straightforward in +principle. However, it's important to keep in mind that a few more things are +necessary to integrate it correctly with a typical Android application. For +example, the call to {@link android.media.MediaPlayer#prepare prepare()} can +take a long time to execute, because +it might involve fetching and decoding media data. So, as is the case with any +method that may take long to execute, you should <strong>never call it from your +application's UI thread</strong>. Doing that will cause the UI to hang until the method returns, +which is a very bad user experience and can cause an ANR (Application Not Responding) error. Even if +you expect your resource to load quickly, remember that anything that takes more than a tenth +of a second to respond in the UI will cause a noticeable pause and will give +the user the impression that your application is slow.</p> + +<p>To avoid hanging your UI thread, spawn another thread to +prepare the {@link android.media.MediaPlayer} and notify the main thread when done. However, while +you could write the threading logic +yourself, this pattern is so common when using {@link android.media.MediaPlayer} that the framework +supplies a convenient way to accomplish this task by using the +{@link android.media.MediaPlayer#prepareAsync prepareAsync()} method. This method +starts preparing the media in the background and returns immediately. When the media +is done preparing, the {@link android.media.MediaPlayer.OnPreparedListener#onPrepared onPrepared()} +method of the {@link android.media.MediaPlayer.OnPreparedListener +MediaPlayer.OnPreparedListener}, configured through +{@link android.media.MediaPlayer#setOnPreparedListener setOnPreparedListener()} is called.</p> + +<h3 id='#managestate'>Managing State</h3> + +<p>Another aspect of a {@link android.media.MediaPlayer} that you should keep in mind is +that it's state-based. That is, the {@link android.media.MediaPlayer} has an internal state +that you must always be aware of when writing your code, because certain operations +are only valid when then player is in specific states. If you perform an operation while in the +wrong state, the system may throw an exception or cause other undesireable behaviors.</p> + +<p>The documentation in the +{@link android.media.MediaPlayer MediaPlayer} class shows a complete state diagram, +that clarifies which methods move the {@link android.media.MediaPlayer} from one state to another. +For example, when you create a new {@link android.media.MediaPlayer}, it is in the <em>Idle</em> +state. At that point, you should initialize it by calling +{@link android.media.MediaPlayer#setDataSource setDataSource()}, bringing it +to the <em>Initialized</em> state. After that, you have to prepare it using either the +{@link android.media.MediaPlayer#prepare prepare()} or +{@link android.media.MediaPlayer#prepareAsync prepareAsync()} method. When +the {@link android.media.MediaPlayer} is done preparing, it will then enter the <em>Prepared</em> +state, which means you can call {@link android.media.MediaPlayer#start start()} +to make it play the media. At that point, as the diagram illustrates, +you can move between the <em>Started</em>, <em>Paused</em> and <em>PlaybackCompleted</em> states by +calling such methods as +{@link android.media.MediaPlayer#start start()}, +{@link android.media.MediaPlayer#pause pause()}, and +{@link android.media.MediaPlayer#seekTo seekTo()}, +amongst others. When you +call {@link android.media.MediaPlayer#stop stop()}, however, notice that you +cannot call {@link android.media.MediaPlayer#start start()} again until you +prepare the {@link android.media.MediaPlayer} again.</p> + +<p>Always keep <a href='{@docRoot}images/mediaplayer_state_diagram.gif'>the state diagram</a> +in mind when writing code that interacts with a +{@link android.media.MediaPlayer} object, because calling its methods from the wrong state is a +common cause of bugs.</p> + +<h3 id='#releaseplayer'>Releasing the MediaPlayer</h3> + +<p>A {@link android.media.MediaPlayer MediaPlayer} can consume valuable +system resources. +Therefore, you should always take extra precautions to make sure you are not +hanging on to a {@link android.media.MediaPlayer} instance longer than necessary. When you +are done with it, you should always call +{@link android.media.MediaPlayer#release release()} to make sure any +system resources allocated to it are properly released. For example, if you are +using a {@link android.media.MediaPlayer} and your activity receives a call to {@link +android.app.Activity#onStop onStop()}, you must release the {@link android.media.MediaPlayer}, +because it +makes little sense to hold on to it while your activity is not interacting with +the user (unless you are playing media in the background, which is discussed in the next section). +When your activity is resumed or restarted, of course, you need to +create a new {@link android.media.MediaPlayer} and prepare it again before resuming playback.</p> + +<p>Here's how you should release and then nullify your {@link android.media.MediaPlayer}:</p> <pre> - MediaPlayer mp = MediaPlayer.create(context, R.raw.sound_file_1); - mp.start(); +mediaPlayer.release(); +mediaPlayer = null; </pre> -<p>To stop playback, call {@link android.media.MediaPlayer#stop() stop()}. If -you wish to later replay the media, then you must -{@link android.media.MediaPlayer#reset() reset()} and -{@link android.media.MediaPlayer#prepare() prepare()} the MediaPlayer object -before calling {@link android.media.MediaPlayer#start() start()} again. -(<code>create()</code> calls <code>prepare()</code> the first time.)</p> -<p>To pause playback, call {@link android.media.MediaPlayer#pause() pause()}. -Resume playback from where you paused with -{@link android.media.MediaPlayer#start() start()}.</p> - -<h3 id="playfile">Playing from a File or Stream</h3> -<p>You can play back media files from the filesystem or a web URL:</p> -<ol> - <li>Create an instance of the <code>MediaPlayer</code> using <code>new</code></li> - <li>Call {@link android.media.MediaPlayer#setDataSource setDataSource()} - with a String containing the path (local filesystem or URL) - to the file you want to play</li> - <li>First {@link android.media.MediaPlayer#prepare prepare()} then - {@link android.media.MediaPlayer#start() start()} on the instance:</li> -</ol> + +<p>As an example, consider the problems that could happen if you +forgot to release the {@link android.media.MediaPlayer} when your activity is stopped, but create a +new one when the activity starts again. As you may know, when the user changes the +screen orientation (or changes the device configuration in another way), +the system handles that by restarting the activity (by default), so you might quickly +consume all of the system resources as the user +rotates the device back and forth between portrait and landscape, because at each +orientation change, you create a new {@link android.media.MediaPlayer} that you never +release. (For more information about runtime restarts, see <a +href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a>.)</p> + +<p>You may be wondering what happens if you want to continue playing +"background media" even when the user leaves your activity, much in the same +way that the built-in Music application behaves. In this case, what you need is +a {@link android.media.MediaPlayer MediaPlayer} controlled by a {@link android.app.Service}, as +discussed in <a href="mpandservices">Using a Service with MediaPlayer</a>.</p> + +<h2 id="mpandservices">Using a Service with MediaPlayer</h2> + +<p>If you want your media to play in the background even when your application +is not onscreen—that is, you want it to continue playing while the user is +interacting with other applications—then you must start a +{@link android.app.Service Service} and control the +{@link android.media.MediaPlayer MediaPlayer} instance from there. +You should be careful about this setup, because the user and the system have expectations +about how an application running a background service should interact with the rest of the +system. If your application does not fulfil those expectations, the user may +have a bad experience. This section describes the main issues that you should be +aware of and offers suggestions about how to approach them.</p> + + +<h3 id="asyncprepare">Running asynchronously</h3> + +<p>First of all, like an {@link android.app.Activity Activity}, all work in a +{@link android.app.Service Service} is done in a single thread by +default—in fact, if you're running an activity and a service from the same application, they +use the same thread (the "main thread") by default. Therefore, services need to +process incoming intents quickly +and never perform lengthy computations when responding to them. If any heavy +work or blocking calls are expected, you must do those tasks asynchronously: either from +another thread you implement yourself, or using the framework's many facilities +for asynchronous processing.</p> + +<p>For instance, when using a {@link android.media.MediaPlayer} from your main thread, +you should call {@link android.media.MediaPlayer#prepareAsync prepareAsync()} rather than +{@link android.media.MediaPlayer#prepare prepare()}, and implement +a {@link android.media.MediaPlayer.OnPreparedListener MediaPlayer.OnPreparedListener} +in order to be notified when the preparation is complete and you can start playing. +For example:</p> + <pre> - MediaPlayer mp = new MediaPlayer(); - mp.setDataSource(PATH_TO_FILE); - mp.prepare(); - mp.start(); +public class MyService extends Service implements MediaPlayer.OnPreparedListener { + private static final ACTION_PLAY = "com.example.action.PLAY"; + MediaPlayer mMediaPlayer = null; + + public int onStartCommand(Intent intent, int flags, int startId) { + ... + if (intent.getAction().equals(ACTION_PLAY)) { + mMediaPlayer = ... // initialize it here + mMediaPlayer.setOnPreparedListener(this); + mMediaPlayer.prepareAsync(); // prepare async to not block main thread + } + } + + /** Called when MediaPlayer is ready */ + public void onPrepared(MediaPlayer player) { +Â Â player.start(); + } +} </pre> -<p>{@link android.media.MediaPlayer#stop() stop()} and -{@link android.media.MediaPlayer#pause() pause()} work the same as discussed -above.</p> - <p class="note"><strong>Note:</strong> - <code>IllegalArgumentException</code> and <code>IOException</code> either - need to be caught or passed on when using <code>setDataSource()</code>, since - the file you are referencing may not exist.</p> -<p class="note"><strong>Note:</strong> -If you're passing a URL to an online media file, the file must be capable of -progressive download.</p> -<h3 id="jet">Playing JET content</h3> -<p>The Android platform includes a JET engine that lets you add interactive playback of JET audio content in your applications. You can create JET content for interactive playback using the JetCreator authoring application that ships with the SDK. To play and manage JET content from your application, use the {@link android.media.JetPlayer JetPlayer} class.</p> -<p>For a description of JET concepts and instructions on how to use the JetCreator authoring tool, see the <a href="{@docRoot}guide/topics/media/jet/jetcreator_manual.html">JetCreator User Manual</a>. The tool is available fully-featured on the OS X and Windows platforms and the Linux version supports all the content creation features, but not the auditioning of the imported assets. </p> +<h3 id="asyncerror">Handling asynchronous errors</h3> + +<p>On synchronous operations, errors would normally +be signaled with an exception or an error code, but whenever you use asynchronous +resources, you should make sure your application is notified +of errors appropriately. In the case of a {@link android.media.MediaPlayer MediaPlayer}, +you can accomplish this by implementing a +{@link android.media.MediaPlayer.OnErrorListener MediaPlayer.OnErrorListener} and +setting it in your {@link android.media.MediaPlayer} instance:</p> + +<pre> +public class MyService extends Service implements MediaPlayer.OnErrorListener { + MediaPlayer mMediaPlayer; + + public void initMediaPlayer() { + // ...initialize the MediaPlayer here... + + mMediaPlayer.setOnErrorListener(this); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + // ... react appropriately ... + // The MediaPlayer has moved to the Error state, must be reset! + } +} +</pre> + +<p>It's important to remember that when an error occurs, the {@link android.media.MediaPlayer} +moves to the <em>Error</em> state (see the documentation for the +{@link android.media.MediaPlayer MediaPlayer} class for the full state diagram) +and you must reset it before you can use it again. + + +<h3 id="wakelocks">Using wake locks</h3> + +<p>When designing applications that play media +in the background, the device may go to sleep +while your service is running. Because the Android system tries to conserve +battery while the device is sleeping, the system tries to shut off any +of the phone's features that are +not necessary, including the CPU and the WiFi hardware. +However, if your service is playing or streaming music, you want to prevent +the system from interfering with your playback.</p> + +<p>In order to ensure that your service continues to run under +those conditions, you have to use "wake locks." A wake lock is a way to signal to +the system that your application is using some feature that should +stay available even if the phone is idle.</p> + +<p class="caution"><strong>Notice:</strong> You should always use wake locks sparingly and hold them +only for as long as truly necessary, because they significantly reduce the battery life of the +device.</p> -<p>Here's an example of how to set up JET playback from a .jet file stored on the SD card:</p> +<p>To ensure that the CPU continues running while your {@link android.media.MediaPlayer} is +playing, call the {@link android.media.MediaPlayer#setWakeMode +setWakeMode()} method when initializing your {@link android.media.MediaPlayer}. Once you do, +the {@link android.media.MediaPlayer} holds the specified lock while playing and releases the lock +when paused or stopped:</p> <pre> -JetPlayer myJet = JetPlayer.getJetPlayer(); -myJet.loadJetFile("/sdcard/level1.jet"); +mMediaPlayer = new MediaPlayer(); +// ... other initialization here ... +mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); +</pre> + +<p>However, the wake lock acquired in this example guarantees only that the CPU remains awake. If +you are streaming media over the +network and you are using Wi-Fi, you probably want to hold a +{@link android.net.wifi.WifiManager.WifiLock WifiLock} as +well, which you must acquire and release manually. So, when you start preparing the +{@link android.media.MediaPlayer} with the remote URL, you should create and acquire the Wi-Fi lock. +For example:</p> + +<pre> +WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)) + .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock"); + +wifiLock.acquire(); +</pre> + +<p>When you pause or stop your media, or when you no longer need the +network, you should release the lock:</p> + +<pre> +wifiLock.release(); +</pre> + + +<h3 id="foregroundserv">Running as a foreground service</h3> + +<p>Services are often used for performing background tasks, such as fetching emails, +synchronizing data, downloading content, amongst other possibilities. In these +cases, the user is not actively aware of the service's execution, and probably +wouldn't even notice if some of these services were interrupted and later restarted.</p> + +<p>But consider the case of a service that is playing music. Clearly this is a service that the user +is actively aware of and the experience would be severely affected by any interruptions. +Additionally, it's a service that the user will likely wish to interact with during its execution. +In this case, the service should run as a "foreground service." A +foreground service holds a higher level of importance within the system—the system will +almost never kill the service, because it is of immediate importance to the user. When running +in the foreground, the service also must provide a status bar notification to ensure that users are +aware of the running service and allow them to open an activity that can interact with the +service.</p> + +<p>In order to turn your service into a foreground service, you must create a +{@link android.app.Notification Notification} for the status bar and call +{@link android.app.Service#startForeground startForeground()} from the {@link +android.app.Service}. For example:</p> + +<pre>String songName; +// assign the song name to songName +PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0, +Â Â Â Â Â Â Â Â new Intent(getApplicationContext(), MainActivity.class), +Â Â Â Â Â Â Â Â PendingIntent.FLAG_UPDATE_CURRENT); +Notification notification = new Notification(); +notification.tickerText = text; +notification.icon = R.drawable.play0; +notification.flags |= Notification.FLAG_ONGOING_EVENT; +notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample", +Â Â Â Â Â Â Â Â "Playing: " + songName, pi); +startForeground(NOTIFICATION_ID, notification); +</pre> + +<p>While your service is running in the foreground, the notification you +configured is visible in the notification area of the device. If the user +selects the notification, the system invokes the {@link android.app.PendingIntent} you supplied. In +the example above, it opens an activity ({@code MainActivity}).</p> + +<p>Figure 1 shows how your notification appears to the user:</p> + +<img src='images/notification1.png' /> + +<img src='images/notification2.png' /> +<p class="img-caption"><strong>Figure 1.</strong> Screenshots of a foreground service's notification, showing the notification icon in the status bar (left) and the expanded view (right).</p> + +<p>You should only hold on to the "foreground service" status while your +service is actually performing something the user is actively aware of. Once +that is no longer true, you should release it by calling +{@link android.app.Service#stopForeground stopForeground()}:</p> + +<pre> +stopForeground(true); +</pre> + +<p>For more information, see the documentation about <a +href="{@docRoot}guide/topics/fundamentals/services.html#Foreground">Services</a> and +<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>.</p> + + +<h3 id="audiofocus">Handling audio focus</h3> + +<p>Even though only one activity can run at any given time, Android is a +multi-tasking environment. This poses a particular challenge to applications +that use audio, because there is only one audio output and there may be several +media services competing for its use. Before Android 2.2, there was no built-in +mechanism to address this issue, which could in some cases lead to a bad user +experience. For example, when a user is listening to +music and another application needs to notify the user of something very important, +the user might not hear the notification tone due to the loud music. Starting with +Android 2.2, the platform offers a way for applications to negotiate their +use of the device's audio output. This mechanism is called Audio Focus.</p> + +<p>When your application needs to output audio such as music or a notification, +you should always request audio focus. Once it has focus, it can use the sound output freely, but it should +always listen for focus changes. If it is notified that it has lost the audio +focus, it should immediately either kill the audio or lower it to a quiet level +(known as "ducking"—there is a flag that indicates which one is appropriate) and only resume +loud playback after it receives focus again.</p> + +<p>Audio Focus is cooperative in nature. That is, applications are expected +(and highly encouraged) to comply with the audio focus guidelines, but the +rules are not enforced by the system. If an application wants to play loud +music even after losing audio focus, nothing in the system will prevent that. +However, the user is more likely to have a bad experience and will be more +likely to uninstall the misbehaving application.</p> + +<p>To request audio focus, you must call +{@link android.media.AudioManager#requestAudioFocus requestAudioFocus()} from the {@link +android.media.AudioManager}, as the example below demonstrates:</p> + +<pre> +AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); +int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + +if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + // could not get audio focus. +} +</pre> + +<p>The first parameter to {@link android.media.AudioManager#requestAudioFocus requestAudioFocus()} +is an {@link android.media.AudioManager.OnAudioFocusChangeListener +AudioManager.OnAudioFocusChangeListener}, +whose {@link android.media.AudioManager.OnAudioFocusChangeListener#onAudioFocusChange +onAudioFocusChange()} method is called whenever there is a change in audio focus. Therefore, you +should also implement this interface on your service and activities. For example:</p> + +<pre> +class MyService extends Service + implements AudioManager.OnAudioFocusChangeListener { + // .... + public void onAudioFocusChange(int focusChange) { + // Do something based on focus change... + } +} +</pre> + +<p>The <code>focusChange</code> parameter tells you how the audio focus has changed, and +can be one of the following values (they are all constants defined in +{@link android.media.AudioManager AudioManager}):</p> + +<ul> +<li>{@link android.media.AudioManager#AUDIOFOCUS_GAIN}: You have gained the audio focus.</li> + +<li>{@link android.media.AudioManager#AUDIOFOCUS_LOSS}: You have lost the audio focus for a +presumably long time. +You must stop all audio playback. Because you should expect not to have focus back +for a long time, this would be a good place to clean up your resources as much +as possible. For example, you should release the {@link android.media.MediaPlayer}.</li> + +<li>{@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}: You have +temporarily lost audio focus, but should receive it back shortly. You must stop +all audio playback, but you can keep your resources because you will probably get +focus back shortly.</li> + +<li>{@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}: You have temporarily +lost audio focus, +but you are allowed to continue to play audio quietly (at a low volume) instead +of killing audio completely.</li> +</ul> + +<p>Here is an example implementation:</p> + +<pre> +public void onAudioFocusChange(int focusChange) { + switch (focusChange) { + case AudioManager.AUDIOFOCUS_GAIN: + // resume playback + if (mMediaPlayer == null) initMediaPlayer(); + else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start(); + mMediaPlayer.setVolume(1.0f, 1.0f); + break; + + case AudioManager.AUDIOFOCUS_LOSS: + // Lost focus for an unbounded amount of time: stop playback and release media player + if (mMediaPlayer.isPlaying()) mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: + // Lost focus for a short time, but we have to stop + // playback. We don't release the media player because playback + // is likely to resume + if (mMediaPlayer.isPlaying()) mMediaPlayer.pause(); + break; + + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: + // Lost focus for a short time, but it's ok to keep playing + // at an attenuated level + if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f); + break; + } +} +</pre> + +<p>Keep in mind that the audio focus APIs are available only with API level 8 (Android 2.2) +and above, so if you want to support previous +versions of Android, you should adopt a backward compatibility strategy that +allows you to use this feature if available, and fall back seamlessly if not.</p> + +<p>You can achieve backward compatibility either by calling the audio focus methods by reflection +or by implementing all the audio focus features in a separate class (say, +<code>AudioFocusHelper</code>). Here is an example of such a class:</p> + +<pre> +public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener { + AudioManager mAudioManager; + + // other fields here, you'll probably hold a reference to an interface + // that you can use to communicate the focus changes to your Service + + public AudioFocusHelper(Context ctx, /* other arguments here */) { + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + // ... + } + + public boolean requestFocus() { + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == + mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + } + + public boolean abandonFocus() { + return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == + mAudioManager.abandonAudioFocus(this); + } + + @Override + public void onAudioFocusChange(int focusChange) { + // let your service know about the focus change + } +} +</pre> + + +<p>You can create an instance of <code>AudioFocusHelper</code> class only if you detect that +the system is running API level 8 or above. For example:</p> + +<pre> +if (android.os.Build.VERSION.SDK_INT >= 8) { + mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this); +} else { + mAudioFocusHelper = null; +} +</pre> + + +<h3 id="cleanup">Performing cleanup</h3> + +<p>As mentioned earlier, a {@link android.media.MediaPlayer} object can consume a significant +amount of system resources, so you should keep it only for as long as you need and call +{@link android.media.MediaPlayer#release release()} when you are done with it. It's important +to call this cleanup method explicitly rather than rely on system garbage collection because +it might take some time before the garbage collector reclaims the {@link android.media.MediaPlayer}, +as it's only sensitive to memory needs and not to shortage of other media-related resources. +So, in the case when you're using a service, you should always override the +{@link android.app.Service#onDestroy onDestroy()} method to make sure you are releasing +the {@link android.media.MediaPlayer}:</p> + +<pre> +public class MyService extends Service { + MediaPlayer mMediaPlayer; + // ... + + @Override + public void onDestroy() { + if (mMediaPlayer != null) mMediaPlayer.release(); + } +} +</pre> + +<p>You should always look for other opportunities to release your {@link android.media.MediaPlayer} +as well, apart from releasing it when being shut down. For example, if you expect not +to be able to play media for an extended period of time (after losing audio focus, for example), +you should definitely release your existing {@link android.media.MediaPlayer} and create it again +later. On the +other hand, if you only expect to stop playback for a very short time, you should probably +hold on to your {@link android.media.MediaPlayer} to avoid the overhead of creating and preparing it +again.</p> + + + +<h2 id="noisyintent">Handling the AUDIO_BECOMING_NOISY Intent</h2> + +<p>Many well-written applications that play audio automatically stop playback when an event +occurs that causes the audio to become noisy (ouput through external speakers). For instance, +this might happen when a user is listening to music through headphones and accidentally +disconnects the headphones from the device. However, this behavior does not happen automatically. +If you don't implement this feature, audio plays out of the device's external speakers, which +might not be what the user wants.</p> + +<p>You can ensure your app stops playing music in these situations by handling +the {@link android.media.AudioManager#ACTION_AUDIO_BECOMING_NOISY} intent, for which you can register a receiver by +adding the following to your manifest:</p> + +<pre> +<receiver android:name=".MusicIntentReceiver"> + <intent-filter> + <action android:name="android.media.AUDIO_BECOMING_NOISY" /> + </intent-filter> +</receiver> +</pre> + +<p>This registers the <code>MusicIntentReceiver</code> class as a broadcast receiver for that +intent. You should then implement this class:</p> + +<pre> +public class MusicIntentReceiver implements android.content.BroadcastReceiver { + @Override + public void onReceive(Context ctx, Intent intent) { + if (intent.getAction().equals( + android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { + // signal your service to stop playback + // (via an Intent, for instance) + } + } +} +</pre> + + + + +<h2 id="viacontentresolver">Retrieving Media from a Content Resolver</h2> + +<p>Another feature that may be useful in a media player application is the ability to +retrieve music that the user has on the device. You can do that by querying the {@link +android.content.ContentResolver} for external media:</p> + +<pre> +ContentResolver contentResolver = getContentResolver(); +Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; +Cursor cursor = contentResolver.query(uri, null, null, null, null); +if (cursor == null) { + // query failed, handle error. +} else if (!cursor.moveToFirst()) { + // no media on the device +} else { + int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE); + int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID); + do { + long thisId = cursor.getLong(idColumn); + String thisTitle = cursor.getString(titleColumn); + // ...process entry... + } while (cursor.moveToNext()); +} +</pre> + +<p>To use this with the {@link android.media.MediaPlayer}, you can do this:</p> + +<pre> +long id = /* retrieve it from somewhere */; +Uri contentUri = ContentUris.withAppendedId( + android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id); + +mMediaPlayer = new MediaPlayer(); +mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); +mMediaPlayer.setDataSource(getApplicationContext(), contentUri); + +// ...prepare and start... +</pre> + + + +<h2 id="jetcontent">Playing JET content</h2> + +<p>The Android platform includes a JET engine that lets you add interactive playback of JET audio +content in your applications. You can create JET content for interactive playback using the +JetCreator authoring application that ships with the SDK. To play and manage JET content from your +application, use the {@link android.media.JetPlayer JetPlayer} class.</p> + +<p>For a description of JET concepts and instructions on how to use the JetCreator authoring tool, +see the <a href="{@docRoot}guide/topics/media/jet/jetcreator_manual.html">JetCreator User +Manual</a>. The tool is available on Windows, OS X, and Linux platforms (Linux does not +support auditioning of imported assets like with the Windows and OS X versions). +</p> + +<p>Here's an example of how to set up JET playback from a <code>.jet</code> file stored on the SD card:</p> + +<pre> +JetPlayer jetPlayer = JetPlayer.getJetPlayer(); +jetPlayer.loadJetFile("/sdcard/level1.jet"); byte segmentId = 0; // queue segment 5, repeat once, use General MIDI, transpose by -1 octave -myJet.queueJetSegment(5, -1, 1, -1, 0, segmentId++); +jetPlayer.queueJetSegment(5, -1, 1, -1, 0, segmentId++); // queue segment 2 -myJet.queueJetSegment(2, -1, 0, 0, 0, segmentId++); +jetPlayer.queueJetSegment(2, -1, 0, 0, 0, segmentId++); -myJet.play(); +jetPlayer.play(); </pre> -<p>The SDK includes an example application — JetBoy — that shows how to use {@link android.media.JetPlayer JetPlayer} to create an interactive music soundtrack in your game. It also illustrates how to use JET events to synchronize music and game logic. The application is located at <code><sdk>/platforms/android-1.5/samples/JetBoy</code>. +<p>The SDK includes an example application — JetBoy — that shows how to use {@link +android.media.JetPlayer JetPlayer} to create an interactive music soundtrack in your game. It also +illustrates how to use JET events to synchronize music and game logic. The application is located at +<code><sdk>/platforms/android-1.5/samples/JetBoy</code>.</p> + -<h2 id="capture">Audio Capture</h2> -<p>Audio capture from the device is a bit more complicated than audio/video playback, but still fairly simple:</p> +<h2 id="audiocapture">Performing Audio Capture</h2> + +<p>Audio capture from the device is a bit more complicated than audio and video playback, but still fairly simple:</p> <ol> - <li>Create a new instance of {@link android.media.MediaRecorder android.media.MediaRecorder} using <code>new</code></li> + <li>Create a new instance of {@link android.media.MediaRecorder android.media.MediaRecorder}.</li> <li>Set the audio source using {@link android.media.MediaRecorder#setAudioSource MediaRecorder.setAudioSource()}. You will probably want to use - <code>MediaRecorder.AudioSource.MIC</code></li> + <code>MediaRecorder.AudioSource.MIC</code>.</li> <li>Set output file format using - {@link android.media.MediaRecorder#setOutputFormat MediaRecorder.setOutputFormat()} + {@link android.media.MediaRecorder#setOutputFormat MediaRecorder.setOutputFormat()}. </li> <li>Set output file name using - {@link android.media.MediaRecorder#setOutputFile MediaRecorder.setOutputFile()} + {@link android.media.MediaRecorder#setOutputFile MediaRecorder.setOutputFile()}. </li> - <li>Set the audio encoder using - {@link android.media.MediaRecorder#setAudioEncoder MediaRecorder.setAudioEncoder()} + <li>Set the audio encoder using + {@link android.media.MediaRecorder#setAudioEncoder MediaRecorder.setAudioEncoder()}. </li> <li>Call {@link android.media.MediaRecorder#prepare MediaRecorder.prepare()} on the MediaRecorder instance.</li> - <li>To start audio capture, call + <li>To start audio capture, call {@link android.media.MediaRecorder#start MediaRecorder.start()}. </li> <li>To stop audio capture, call {@link android.media.MediaRecorder#stop MediaRecorder.stop()}. <li>When you are done with the MediaRecorder instance, call @@ -354,3 +968,4 @@ public class AudioRecordTest extends Activity </pre> + diff --git a/docs/html/guide/topics/usb/adk.jd b/docs/html/guide/topics/usb/adk.jd index 0e3563719553..2e172f55b8bf 100644 --- a/docs/html/guide/topics/usb/adk.jd +++ b/docs/html/guide/topics/usb/adk.jd @@ -54,6 +54,11 @@ page.title=Android Open Accessory Development Kit </li> </ol> + <h2>Download</h2> + <ol> + <li><a href="https://dl-ssl.google.com/android/adk/adk_release_0512.zip">ADK package</a></li> + </ol> + <h2>See also</h2> diff --git a/docs/html/guide/topics/wireless/bluetooth.jd b/docs/html/guide/topics/wireless/bluetooth.jd index 48632ea6f5e1..a6c46d2cad44 100644 --- a/docs/html/guide/topics/wireless/bluetooth.jd +++ b/docs/html/guide/topics/wireless/bluetooth.jd @@ -66,7 +66,7 @@ following:</p> <h2 id="TheBasics">The Basics</h2> -<p>This document describes how to us the Android Bluetooth APIs to accomplish +<p>This document describes how to use the Android Bluetooth APIs to accomplish the four major tasks necessary to communicate using Bluetooth: setting up Bluetooth, finding devices that are either paired or available in the local area, connecting devices, and transferring data between devices.</p> diff --git a/docs/html/resources/resources-data.js b/docs/html/resources/resources-data.js index 097d0041c73d..e8c9ae7e3a0c 100644 --- a/docs/html/resources/resources-data.js +++ b/docs/html/resources/resources-data.js @@ -547,6 +547,16 @@ var ANDROID_RESOURCES = [ } }, { + tags: ['sample', 'new', 'media' ], + path: 'samples/RandomMusicPlayer/index.html', + title: { + en: 'Random Music Player' + }, + description: { + en: 'Demonstrates how to write a multimedia application that plays music from the device and from URLs. It manages media playback from a service and can play music in the background, respecting audio focus changes.' + } + }, + { tags: ['sample', 'new', 'newfeature', 'performance', 'gamedev', 'gl'], path: 'samples/RenderScript/index.html', title: { diff --git a/docs/html/resources/samples/images/randommusicplayer.png b/docs/html/resources/samples/images/randommusicplayer.png Binary files differnew file mode 100644 index 000000000000..e16e0677da00 --- /dev/null +++ b/docs/html/resources/samples/images/randommusicplayer.png diff --git a/docs/html/search.jd b/docs/html/search.jd index 609ade9cd2e2..339ce2ded35c 100644 --- a/docs/html/search.jd +++ b/docs/html/search.jd @@ -1,144 +1,140 @@ page.title=Search Results
@jd:body
-<script src="http://www.google.com/jsapi" type="text/javascript"></script> +<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script src="{@docRoot}assets/jquery-history.js" type="text/javascript"></script>
-<script type="text/javascript"> - var tabIndex = 0; - - google.load('search', '1'); - - function OnLoad() { - document.getElementById("search_autocomplete").style.color = "#000"; - - // create search control - searchControl = new google.search.SearchControl(); - - // use our existing search form and use tabs when multiple searchers are used - drawOptions = new google.search.DrawOptions(); - drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED); - drawOptions.setInput(document.getElementById("search_autocomplete")); - - // configure search result options - searchOptions = new google.search.SearcherOptions(); - searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN); - - // configure each of the searchers, for each tab - devSiteSearcher = new google.search.WebSearch(); - devSiteSearcher.setUserDefinedLabel("All Developers Site"); - devSiteSearcher.setSiteRestriction("http://developer.android.com/"); - - devGuideSearcher = new google.search.WebSearch(); - devGuideSearcher.setUserDefinedLabel("Dev Guide"); - devGuideSearcher.setSiteRestriction("http://developer.android.com/guide/"); - - referenceSearcher = new google.search.WebSearch(); - referenceSearcher.setUserDefinedLabel("Reference"); - referenceSearcher.setSiteRestriction("http://developer.android.com/reference/"); - - blogSearcher = new google.search.WebSearch(); - blogSearcher.setUserDefinedLabel("Blog"); - blogSearcher.setSiteRestriction("http://android-developers.blogspot.com"); - - groupsSearcher = new google.search.WebSearch(); - groupsSearcher.setUserDefinedLabel("Developer Groups"); - groupsSearcher.setSiteRestriction("001283715400630100512:ggqrtvkztwm"); - - sourceSiteSearcher = new google.search.WebSearch(); - sourceSiteSearcher.setUserDefinedLabel("Android Source"); - sourceSiteSearcher.setSiteRestriction("http://source.android.com"); - - homeSiteSearcher = new google.search.WebSearch(); - homeSiteSearcher.setUserDefinedLabel("Android Home"); - homeSiteSearcher.setSiteRestriction("http://www.android.com"); - - // add each searcher to the search control - searchControl.addSearcher(devSiteSearcher, searchOptions); - searchControl.addSearcher(devGuideSearcher, searchOptions); - searchControl.addSearcher(referenceSearcher, searchOptions); - searchControl.addSearcher(groupsSearcher, searchOptions); - searchControl.addSearcher(sourceSiteSearcher, searchOptions); - searchControl.addSearcher(blogSearcher, searchOptions); - - // configure result options - searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET); - searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF); - searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_LONG); - searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING); - - // upon ajax search, refresh the url and search title - searchControl.setSearchStartingCallback(this, function(control, searcher, query) { - // save the tab index from the hash - tabIndex = location.hash.split("&t=")[1]; - - $("#searchTitle").html("search results for <em>" + escapeHTML(query) + "</em>"); - $.history.add('q=' + query + '&t=' + tabIndex); - openTab(); - }); - - // draw the search results box - searchControl.draw(document.getElementById("leftSearchControl"), drawOptions); - - // get query and execute the search - if (location.hash.indexOf("&t=") != -1) { - searchControl.execute(decodeURI(getQuery(location.hash))); - } - - document.getElementById("search_autocomplete").focus(); - addTabListeners(); - } - // End of OnLoad - - - google.setOnLoadCallback(OnLoad, true); - - // when an event on the browser history occurs (back, forward, load) perform a search - $(window).history(function(e, hash) { - var query = decodeURI(getQuery(hash)); - searchControl.execute(query); - - $("#searchTitle").html("search results for <em>" + escapeHTML(query) + "</em>"); - }); - - // forcefully regain key-up event control (previously jacked by search api) - $("#search_autocomplete").keyup(function(event) { - return search_changed(event, false, '/'); - }); - - // open a tab, specified by its array position - function openTab() { - tabIndex = location.hash.split("&t=")[1]; - - // show the appropriate tab - var tabHeaders = $(".gsc-tabHeader"); - $(tabHeaders[tabIndex]).click(); - } - - // add event listeners to each tab so we can track the browser history - function addTabListeners() { - var tabHeaders = $(".gsc-tabHeader"); - for (var i = 0; i < tabHeaders.length; i++) { - $(tabHeaders[i]).attr("id",i).click(function() { - var tabHeaders = $(".gsc-tabHeader"); +<script type="text/javascript">
+ var tabIndex = 0;
+
+ google.load('search', '1');
+
+ function OnLoad() {
+ document.getElementById("search_autocomplete").style.color = "#000";
+
+ // create search control
+ searchControl = new google.search.SearchControl();
+
+ // use our existing search form and use tabs when multiple searchers are used
+ drawOptions = new google.search.DrawOptions();
+ drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
+ drawOptions.setInput(document.getElementById("search_autocomplete"));
+
+ // configure search result options
+ searchOptions = new google.search.SearcherOptions();
+ searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
+
+ // configure each of the searchers, for each tab
+ devSiteSearcher = new google.search.WebSearch();
+ devSiteSearcher.setUserDefinedLabel("All");
+ devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
+
+ devGuideSearcher = new google.search.WebSearch();
+ devGuideSearcher.setUserDefinedLabel("Dev Guide");
+ devGuideSearcher.setSiteRestriction("http://developer.android.com/guide/");
+
+ referenceSearcher = new google.search.WebSearch();
+ referenceSearcher.setUserDefinedLabel("Reference");
+ referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
+
+ blogSearcher = new google.search.WebSearch();
+ blogSearcher.setUserDefinedLabel("Blog");
+ blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
+
+ groupsSearcher = new google.search.WebSearch();
+ groupsSearcher.setUserDefinedLabel("Developer Groups");
+ groupsSearcher.setSiteRestriction("001283715400630100512:ggqrtvkztwm");
+
+ sourceSiteSearcher = new google.search.WebSearch();
+ sourceSiteSearcher.setUserDefinedLabel("Android Source");
+ sourceSiteSearcher.setSiteRestriction("http://source.android.com");
+
+ // add each searcher to the search control
+ searchControl.addSearcher(devSiteSearcher, searchOptions);
+ searchControl.addSearcher(devGuideSearcher, searchOptions);
+ searchControl.addSearcher(referenceSearcher, searchOptions);
+ searchControl.addSearcher(groupsSearcher, searchOptions);
+ searchControl.addSearcher(sourceSiteSearcher, searchOptions);
+ searchControl.addSearcher(blogSearcher, searchOptions);
+
+ // configure result options
+ searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
+ searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
+ searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_LONG);
+ searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
+
+ // upon ajax search, refresh the url and search title
+ searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
+ // save the tab index from the hash
+ tabIndex = location.hash.split("&t=")[1];
+
+ $("#searchTitle").html("search results for <em>" + escapeHTML(query) + "</em>");
+ $.history.add('q=' + query + '&t=' + tabIndex);
+ openTab();
+ });
+
+ // draw the search results box
+ searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
+
+ // get query and execute the search
+ if (location.hash.indexOf("&t=") != -1) {
+ searchControl.execute(decodeURI(getQuery(location.hash)));
+ }
+
+ document.getElementById("search_autocomplete").focus();
+ addTabListeners();
+ }
+ // End of OnLoad
+
+
+ google.setOnLoadCallback(OnLoad, true);
+
+ // when an event on the browser history occurs (back, forward, load) perform a search
+ $(window).history(function(e, hash) {
+ var query = decodeURI(getQuery(hash));
+ searchControl.execute(query);
+
+ $("#searchTitle").html("search results for <em>" + escapeHTML(query) + "</em>");
+ });
+
+ // forcefully regain key-up event control (previously jacked by search api)
+ $("#search_autocomplete").keyup(function(event) {
+ return search_changed(event, false, '/');
+ });
+
+ // open a tab, specified by its array position
+ function openTab() {
+ tabIndex = location.hash.split("&t=")[1];
+
+ // show the appropriate tab
+ var tabHeaders = $(".gsc-tabHeader");
+ $(tabHeaders[tabIndex]).click();
+ }
+
+ // add event listeners to each tab so we can track the browser history
+ function addTabListeners() {
+ var tabHeaders = $(".gsc-tabHeader");
+ for (var i = 0; i < tabHeaders.length; i++) {
+ $(tabHeaders[i]).attr("id",i).click(function() {
+ var tabHeaders = $(".gsc-tabHeader");
var tabIndex = $(this).attr("id");
- $.history.add('q=' + getQuery(location.hash) + '&t=' + tabIndex); // update the hash with the new tab - }); - } - } - + $.history.add('q=' + getQuery(location.hash) + '&t=' + tabIndex); // update the hash with the new tab
+ });
+ }
+ }
+
function getQuery(hash) {
- var hashParts = hash.split('&t='); - var queryParts = hashParts[0].split('='); - return queryParts[1]; - } - - /* returns the given string with all HTML brackets converted to entities - TODO: move this to the site's JS library */ - function escapeHTML(string) { - return string.replace(/</g,"<") - .replace(/>/g,">"); - } - + var hashParts = hash.split('&t=');
+ var queryParts = hashParts[0].split('=');
+ return queryParts[1];
+ }
+
+ /* returns the given string with all HTML brackets converted to entities
+ TODO: move this to the site's JS library */
+ function escapeHTML(string) {
+ return string.replace(/</g,"<")
+ .replace(/>/g,">");
+ }
+
</script>
<div id="mainBodyFixed" style="width:auto; margin:20px">
diff --git a/drm/common/DrmSupportInfo.cpp b/drm/common/DrmSupportInfo.cpp index 3dee435e749b..5400bdd45cb8 100644 --- a/drm/common/DrmSupportInfo.cpp +++ b/drm/common/DrmSupportInfo.cpp @@ -43,6 +43,10 @@ bool DrmSupportInfo::operator==(const DrmSupportInfo& drmSupportInfo) const { } bool DrmSupportInfo::isSupportedMimeType(const String8& mimeType) const { + if (String8("") == mimeType) { + return false; + } + for (unsigned int i = 0; i < mMimeTypeVector.size(); i++) { const String8 item = mMimeTypeVector.itemAt(i); diff --git a/drm/common/IDrmManagerService.cpp b/drm/common/IDrmManagerService.cpp index 458f1b6d1d87..2d8e877b97d1 100644 --- a/drm/common/IDrmManagerService.cpp +++ b/drm/common/IDrmManagerService.cpp @@ -107,6 +107,7 @@ static void clearDecryptHandle(DecryptHandle* handle) { handle->decryptInfo = NULL; } handle->copyControlVector.clear(); + handle->extendedData.clear(); } int BpDrmManagerService::addUniqueId(int uniqueId) { diff --git a/drm/java/android/drm/DrmRights.java b/drm/java/android/drm/DrmRights.java index 59079562b07d..ef9c21dc7dc4 100755 --- a/drm/java/android/drm/DrmRights.java +++ b/drm/java/android/drm/DrmRights.java @@ -107,31 +107,24 @@ public class DrmRights { /** * Creates a <code>DrmRights</code> object with the given parameters. - *<p> - * The application can pass the processed data as a <code>String</code> or as binary data. - *<p> - * The following code snippet shows how to pass the processed data as a <code>String</code>: - *<p> - * new DrmRights(data.getBytes(), mimeType) - *<p> - * The following code snippet shows how to pass the processed data as binary data: - *<p> - * new DrmRights(binaryData[], mimeType) * - * @param data A {@link ProcessedData} object. + * @param data A {@link ProcessedData} object containing rights information. + * data could be null because it's optional for some DRM schemes. * @param mimeType The MIME type. */ public DrmRights(ProcessedData data, String mimeType) { - mData = data.getData(); - - String accountId = data.getAccountId(); - if (null != accountId && !accountId.equals("")) { - mAccountId = accountId; - } - - String subscriptionId = data.getSubscriptionId(); - if (null != subscriptionId && !subscriptionId.equals("")) { - mSubscriptionId = subscriptionId; + if (data != null) { + mData = data.getData(); + + String accountId = data.getAccountId(); + if (null != accountId && !accountId.equals("")) { + mAccountId = accountId; + } + + String subscriptionId = data.getSubscriptionId(); + if (null != subscriptionId && !subscriptionId.equals("")) { + mSubscriptionId = subscriptionId; + } } mMimeType = mimeType; diff --git a/drm/libdrmframework/DrmManagerClientImpl.cpp b/drm/libdrmframework/DrmManagerClientImpl.cpp index a57dd98f32db..a36bd4ae4023 100644 --- a/drm/libdrmframework/DrmManagerClientImpl.cpp +++ b/drm/libdrmframework/DrmManagerClientImpl.cpp @@ -145,7 +145,6 @@ DrmInfo* DrmManagerClientImpl::acquireDrmInfo( status_t DrmManagerClientImpl::saveRights(int uniqueId, const DrmRights& drmRights, const String8& rightsPath, const String8& contentPath) { - status_t status = DRM_ERROR_UNKNOWN; return getDrmManagerService()->saveRights( uniqueId, drmRights, rightsPath, contentPath); } diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index a4734fffd361..7e03e1c4669a 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -405,8 +405,11 @@ public class BitmapDrawable extends Drawable { @Override public void setAlpha(int alpha) { - mBitmapState.mPaint.setAlpha(alpha); - invalidateSelf(); + int oldAlpha = mBitmapState.mPaint.getAlpha(); + if (alpha != oldAlpha) { + mBitmapState.mPaint.setAlpha(alpha); + invalidateSelf(); + } } @Override diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java index 9a3ca4023898..483fa5612944 100644 --- a/graphics/java/android/graphics/drawable/TransitionDrawable.java +++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java @@ -187,8 +187,20 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba final int alpha = mAlpha; final boolean crossFade = mCrossFade; final ChildDrawable[] array = mLayerState.mChildren; - Drawable d; + if (done) { + // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw + // the appropriate drawable[s] and return + if (!crossFade || alpha == 0) { + array[0].mDrawable.draw(canvas); + } + if (alpha == 0xFF) { + array[1].mDrawable.draw(canvas); + } + return; + } + + Drawable d; d = array[0].mDrawable; if (crossFade) { d.setAlpha(255 - alpha); diff --git a/graphics/java/android/renderscript/RSTextureView.java b/graphics/java/android/renderscript/RSTextureView.java index f63ae8dcd68e..6046ee14656d 100644 --- a/graphics/java/android/renderscript/RSTextureView.java +++ b/graphics/java/android/renderscript/RSTextureView.java @@ -94,6 +94,12 @@ public class RSTextureView extends TextureView implements TextureView.SurfaceTex } } + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + //Log.e(RenderScript.LOG_TAG, "onSurfaceTextureUpdated"); + mSurfaceTexture = surface; + } + /** * Inform the view that the activity is paused. The owner of this view must * call this method when the activity is paused. Calling this method will diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h index deade5ec5238..99b72ad86415 100644 --- a/include/media/stagefright/MetaData.h +++ b/include/media/stagefright/MetaData.h @@ -118,6 +118,9 @@ enum { // The language code for this media kKeyMediaLanguage = 'lang', // cstring + + // To store the timed text format data + kKeyTextFormatData = 'text', // raw data }; enum { diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index afab26a94fec..e43f6e55fb23 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -101,8 +101,14 @@ void DisplayList::clearResources() { } mBitmapResources.clear(); + for (size_t i = 0; i < mFilterResources.size(); i++) { + caches.resourceCache.decrementRefcount(mFilterResources.itemAt(i)); + } + mFilterResources.clear(); + for (size_t i = 0; i < mShaders.size(); i++) { caches.resourceCache.decrementRefcount(mShaders.itemAt(i)); + caches.resourceCache.destructor(mShaders.itemAt(i)); } mShaders.clear(); @@ -151,11 +157,18 @@ void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorde caches.resourceCache.incrementRefcount(resource); } + const Vector<SkiaColorFilter*> &filterResources = recorder.getFilterResources(); + for (size_t i = 0; i < filterResources.size(); i++) { + SkiaColorFilter* resource = filterResources.itemAt(i); + mFilterResources.add(resource); + caches.resourceCache.incrementRefcount(resource); + } + const Vector<SkiaShader*> &shaders = recorder.getShaders(); for (size_t i = 0; i < shaders.size(); i++) { - SkiaShader* shader = shaders.itemAt(i); - mShaders.add(shader); - caches.resourceCache.incrementRefcount(shader); + SkiaShader* resource = shaders.itemAt(i); + mShaders.add(resource); + caches.resourceCache.incrementRefcount(resource); } const Vector<SkPaint*> &paints = recorder.getPaints(); @@ -873,21 +886,27 @@ void DisplayListRenderer::reset() { Caches& caches = Caches::getInstance(); for (size_t i = 0; i < mBitmapResources.size(); i++) { - SkBitmap* resource = mBitmapResources.itemAt(i); - caches.resourceCache.decrementRefcount(resource); + caches.resourceCache.decrementRefcount(mBitmapResources.itemAt(i)); } mBitmapResources.clear(); + for (size_t i = 0; i < mFilterResources.size(); i++) { + caches.resourceCache.decrementRefcount(mFilterResources.itemAt(i)); + } + mFilterResources.clear(); + for (size_t i = 0; i < mShaders.size(); i++) { - caches.resourceCache.decrementRefcount(mShaders.itemAt(i)); + caches.resourceCache.decrementRefcount(mShaders.itemAt(i)); } mShaders.clear(); mShaderMap.clear(); mPaints.clear(); mPaintMap.clear(); + mPaths.clear(); mPathMap.clear(); + mMatrices.clear(); } diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index dcf2cf2ddb39..b83259f82450 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -292,6 +292,10 @@ public: return mBitmapResources; } + const Vector<SkiaColorFilter*>& getFilterResources() const { + return mFilterResources; + } + const Vector<SkiaShader*>& getShaders() const { return mShaders; } @@ -308,10 +312,6 @@ public: return mMatrices; } - const Vector<SkiaColorFilter*>& getFilterResources() const { - return mFilterResources; - } - private: void insertRestoreToCount() { if (mRestoreSaveCount >= 0) { @@ -419,7 +419,9 @@ private: inline void addMatrix(SkMatrix* matrix) { // Copying the matrix is cheap and prevents against the user changing the original // matrix before the operation that uses it - addInt((int) new SkMatrix(*matrix)); + SkMatrix* copy = new SkMatrix(*matrix); + addInt((int) copy); + mMatrices.add(copy); } inline void addBitmap(SkBitmap* bitmap) { @@ -429,8 +431,7 @@ private: // which doesn't seem worth the extra cycles for this unlikely case. addInt((int) bitmap); mBitmapResources.add(bitmap); - Caches& caches = Caches::getInstance(); - caches.resourceCache.incrementRefcount(bitmap); + Caches::getInstance().resourceCache.incrementRefcount(bitmap); } inline void addShader(SkiaShader* shader) { @@ -454,8 +455,7 @@ private: inline void addColorFilter(SkiaColorFilter* colorFilter) { addInt((int) colorFilter); mFilterResources.add(colorFilter); - Caches& caches = Caches::getInstance(); - caches.resourceCache.incrementRefcount(colorFilter); + Caches::getInstance().resourceCache.incrementRefcount(colorFilter); } Vector<SkBitmap*> mBitmapResources; diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 77e63d70f474..dfb5d3b510b2 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -166,13 +166,13 @@ void LayerRenderer::generateMesh() { // Layers management /////////////////////////////////////////////////////////////////////////////// -Layer* LayerRenderer::createTextureLayer() { +Layer* LayerRenderer::createTextureLayer(bool isOpaque) { LAYER_RENDERER_LOGD("Creating new texture layer"); Layer* layer = new Layer(0, 0); layer->isCacheable = false; layer->isTextureLayer = true; - layer->blend = true; + layer->blend = !isOpaque; layer->empty = true; layer->fbo = 0; layer->colorFilter = NULL; @@ -270,8 +270,9 @@ bool LayerRenderer::resizeLayer(Layer* layer, uint32_t width, uint32_t height) { } void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height, - GLenum renderTarget, float* transform) { + bool isOpaque, GLenum renderTarget, float* transform) { if (layer) { + layer->blend = !isOpaque; layer->width = width; layer->height = height; layer->layer.set(0.0f, 0.0f, width, height); diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h index 797dfc63e5bb..224657397dd1 100644 --- a/libs/hwui/LayerRenderer.h +++ b/libs/hwui/LayerRenderer.h @@ -55,11 +55,11 @@ public: Region* getRegion(); GLint getTargetFbo(); - static Layer* createTextureLayer(); + static Layer* createTextureLayer(bool isOpaque); static Layer* createLayer(uint32_t width, uint32_t height, bool isOpaque = false); static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height); static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height, - GLenum renderTarget, float* transform); + bool isOpaque, GLenum renderTarget, float* transform); static void destroyLayer(Layer* layer); static void destroyLayerDeferred(Layer* layer); static bool copyLayer(Layer* layer, SkBitmap* bitmap); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 88774c6dff47..1c06a0b3a107 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -639,7 +639,7 @@ void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) { setupDrawTextureTransform(); setupDrawColor(alpha, alpha, alpha, alpha); setupDrawColorFilter(); - setupDrawBlending(layer->blend, layer->mode); + setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode); setupDrawProgram(); setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom); setupDrawPureColorUniforms(); diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index 9aade5175c56..cd2c40506869 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -48,9 +48,6 @@ ResourceCache::~ResourceCache() { void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType) { Mutex::Autolock _l(mLock); - for (size_t i = 0; i < mCache->size(); ++i) { - void* ref = mCache->valueAt(i); - } ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL; if (ref == NULL || mCache->size() == 0) { ref = new ResourceReference(resourceType); @@ -144,7 +141,6 @@ void ResourceCache::destructor(SkPath* resource) { ref->destroyed = true; if (ref->refCount == 0) { deleteResourceReference(resource, ref); - return; } } @@ -162,7 +158,6 @@ void ResourceCache::destructor(SkBitmap* resource) { ref->destroyed = true; if (ref->refCount == 0) { deleteResourceReference(resource, ref); - return; } } @@ -180,7 +175,6 @@ void ResourceCache::destructor(SkiaShader* resource) { ref->destroyed = true; if (ref->refCount == 0) { deleteResourceReference(resource, ref); - return; } } @@ -195,7 +189,6 @@ void ResourceCache::destructor(SkiaColorFilter* resource) { ref->destroyed = true; if (ref->refCount == 0) { deleteResourceReference(resource, ref); - return; } } @@ -209,36 +202,32 @@ void ResourceCache::deleteResourceReference(void* resource, ResourceReference* r } if (ref->destroyed) { switch (ref->resourceType) { - case kBitmap: - { - SkBitmap* bitmap = (SkBitmap*)resource; + case kBitmap: { + SkBitmap* bitmap = (SkBitmap*) resource; if (Caches::hasInstance()) { Caches::getInstance().textureCache.removeDeferred(bitmap); } delete bitmap; } break; - case kPath: - { - SkPath* path = (SkPath*)resource; + case kPath: { + SkPath* path = (SkPath*) resource; if (Caches::hasInstance()) { Caches::getInstance().pathCache.removeDeferred(path); } delete path; } break; - case kShader: - { - SkiaShader* shader = (SkiaShader*)resource; + case kShader: { + SkiaShader* shader = (SkiaShader*) resource; if (Caches::hasInstance()) { Caches::getInstance().gradientCache.removeDeferred(shader->getSkShader()); } delete shader; } break; - case kColorFilter: - { - SkiaColorFilter* filter = (SkiaColorFilter*)resource; + case kColorFilter: { + SkiaColorFilter* filter = (SkiaColorFilter*) resource; delete filter; } break; diff --git a/libs/hwui/SkiaColorFilter.h b/libs/hwui/SkiaColorFilter.h index bf45e13bc0f2..1bf475c2268e 100644 --- a/libs/hwui/SkiaColorFilter.h +++ b/libs/hwui/SkiaColorFilter.h @@ -59,7 +59,7 @@ struct SkiaColorFilter { return mType; } - SkColorFilter *getSkColorFilter() { + SkColorFilter* getSkColorFilter() { return mSkFilter; } diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 255773067919..33312d195656 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1568,7 +1568,14 @@ public class MediaPlayer return; case MEDIA_TIMED_TEXT: if (mOnTimedTextListener != null) { - mOnTimedTextListener.onTimedText(mMediaPlayer, (String)msg.obj); + if (msg.obj == null) { + mOnTimedTextListener.onTimedText(mMediaPlayer, null); + } else { + if (msg.obj instanceof byte[]) { + TimedText text = new TimedText((byte[])(msg.obj)); + mOnTimedTextListener.onTimedText(mMediaPlayer, text); + } + } } return; @@ -1755,14 +1762,14 @@ public class MediaPlayer public interface OnTimedTextListener { /** - * Called to indicate the video size + * Called to indicate an avaliable timed text * * @param mp the MediaPlayer associated with this callback - * @param text the timed text sample which contains the - * text needed to be displayed. + * @param text the timed text sample which contains the text + * needed to be displayed and the display format. * {@hide} */ - public void onTimedText(MediaPlayer mp, String text); + public void onTimedText(MediaPlayer mp, TimedText text); } /** diff --git a/media/java/android/media/TimedText.java b/media/java/android/media/TimedText.java new file mode 100644 index 000000000000..a055c8bac874 --- /dev/null +++ b/media/java/android/media/TimedText.java @@ -0,0 +1,655 @@ +/* + * 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 android.media; + +import android.os.Parcel; +import android.util.Log; +import java.util.HashMap; +import java.util.Set; +import java.util.List; +import java.util.ArrayList; + +/** + * Class to hold the timed text's metadata. + * + * {@hide} + */ +public class TimedText +{ + private static final int FIRST_PUBLIC_KEY = 1; + + // These keys must be in sync with the keys in TextDescription.h + public static final int KEY_DISPLAY_FLAGS = 1; // int + public static final int KEY_STYLE_FLAGS = 2; // int + public static final int KEY_BACKGROUND_COLOR_RGBA = 3; // int + public static final int KEY_HIGHLIGHT_COLOR_RGBA = 4; // int + public static final int KEY_SCROLL_DELAY = 5; // int + public static final int KEY_WRAP_TEXT = 6; // int + public static final int KEY_START_TIME = 7; // int + public static final int KEY_STRUCT_BLINKING_TEXT_LIST = 8; // List<CharPos> + public static final int KEY_STRUCT_FONT_LIST = 9; // List<Font> + public static final int KEY_STRUCT_HIGHLIGHT_LIST = 10; // List<CharPos> + public static final int KEY_STRUCT_HYPER_TEXT_LIST = 11; // List<HyperText> + public static final int KEY_STRUCT_KARAOKE_LIST = 12; // List<Karaoke> + public static final int KEY_STRUCT_STYLE_LIST = 13; // List<Style> + public static final int KEY_STRUCT_TEXT_POS = 14; // TextPos + public static final int KEY_STRUCT_JUSTIFICATION = 15; // Justification + public static final int KEY_STRUCT_TEXT = 16; // Text + + private static final int LAST_PUBLIC_KEY = 16; + + private static final int FIRST_PRIVATE_KEY = 101; + + // The following keys are used between TimedText.java and + // TextDescription.cpp in order to parce the Parcel. + private static final int KEY_GLOBAL_SETTING = 101; + private static final int KEY_LOCAL_SETTING = 102; + private static final int KEY_START_CHAR = 103; + private static final int KEY_END_CHAR = 104; + private static final int KEY_FONT_ID = 105; + private static final int KEY_FONT_SIZE = 106; + private static final int KEY_TEXT_COLOR_RGBA = 107; + + private static final int LAST_PRIVATE_KEY = 107; + + private static final String TAG = "TimedText"; + + private Parcel mParcel = Parcel.obtain(); + private final HashMap<Integer, Object> mKeyObjectMap = + new HashMap<Integer, Object>(); + + private int mDisplayFlags = -1; + private int mBackgroundColorRGBA = -1; + private int mHighlightColorRGBA = -1; + private int mScrollDelay = -1; + private int mWrapText = -1; + + private List<CharPos> mBlinkingPosList = null; + private List<CharPos> mHighlightPosList = null; + private List<Karaoke> mKaraokeList = null; + private List<Font> mFontList = null; + private List<Style> mStyleList = null; + private List<HyperText> mHyperTextList = null; + + private TextPos mTextPos; + private Justification mJustification; + private Text mTextStruct; + + /** + * Helper class to hold the text length and text content of + * one text sample. The member variables in this class are + * read-only. + */ + public class Text { + /** + * The byte-count of this text sample + */ + public int textLen; + + /** + * The text sample + */ + public byte[] text; + + public Text() { } + } + + /** + * Helper class to hold the start char offset and end char offset + * for Blinking Text or Highlight Text. endChar is the end offset + * of the text (startChar + number of characters to be highlighted + * or blinked). The member variables in this class are read-only. + */ + public class CharPos { + /** + * The offset of the start character + */ + public int startChar = -1; + + /** + * The offset of the end character + */ + public int endChar = -1; + + public CharPos() { } + } + + /** + * Helper class to hold the box position to display the text sample. + * The member variables in this class are read-only. + */ + public class TextPos { + /** + * The top position of the text + */ + public int top = -1; + + /** + * The left position of the text + */ + public int left = -1; + + /** + * The bottom position of the text + */ + public int bottom = -1; + + /** + * The right position of the text + */ + public int right = -1; + + public TextPos() { } + } + + /** + * Helper class to hold the justification for text display in the text box. + * The member variables in this class are read-only. + */ + public class Justification { + /** + * horizontalJustification 0: left, 1: centered, -1: right + */ + public int horizontalJustification = -1; + + /** + * verticalJustification 0: top, 1: centered, -1: bottom + */ + public int verticalJustification = -1; + + public Justification() { } + } + + /** + * Helper class to hold the style information to display the text. + * The member variables in this class are read-only. + */ + public class Style { + /** + * The offset of the start character which applys this style + */ + public int startChar = -1; + + /** + * The offset of the end character which applys this style + */ + public int endChar = -1; + + /** + * ID of the font. This ID will be used to choose the font + * to be used from the font list. + */ + public int fontID = -1; + + /** + * True if the characters should be bold + */ + public boolean isBold = false; + + /** + * True if the characters should be italic + */ + public boolean isItalic = false; + + /** + * True if the characters should be underlined + */ + public boolean isUnderlined = false; + + /** + * The size of the font + */ + public int fontSize = -1; + + /** + * To specify the RGBA color: 8 bits each of red, green, blue, + * and an alpha(transparency) value + */ + public int colorRGBA = -1; + + public Style() { } + } + + /** + * Helper class to hold the font ID and name. + * The member variables in this class are read-only. + */ + public class Font { + /** + * The font ID + */ + public int ID = -1; + + /** + * The font name + */ + public String name; + + public Font() { } + } + + /** + * Helper class to hold the karaoke information. + * The member variables in this class are read-only. + */ + public class Karaoke { + /** + * The start time (in milliseconds) to highlight the characters + * specified by startChar and endChar. + */ + public int startTimeMs = -1; + + /** + * The end time (in milliseconds) to highlight the characters + * specified by startChar and endChar. + */ + public int endTimeMs = -1; + + /** + * The offset of the start character to be highlighted + */ + public int startChar = -1; + + /** + * The offset of the end character to be highlighted + */ + public int endChar = -1; + + public Karaoke() { } + } + + /** + * Helper class to hold the hyper text information. + * The member variables in this class are read-only. + */ + public class HyperText { + /** + * The offset of the start character + */ + public int startChar = -1; + + /** + * The offset of the end character + */ + public int endChar = -1; + + /** + * The linked-to URL + */ + public String URL; + + /** + * The "alt" string for user display + */ + public String altString; + + public HyperText() { } + } + + /** + * @param obj the byte array which contains the timed text. + * @throws IllegalArgumentExcept if parseParcel() fails. + * {@hide} + */ + public TimedText(byte[] obj) { + mParcel.unmarshall(obj, 0, obj.length); + + if (!parseParcel()) { + mKeyObjectMap.clear(); + throw new IllegalArgumentException("parseParcel() fails"); + } + } + + /** + * Go over all the records, collecting metadata keys and fields in the + * Parcel. These are stored in mKeyObjectMap for application to retrieve. + * @return false if an error occurred during parsing. Otherwise, true. + */ + private boolean parseParcel() { + mParcel.setDataPosition(0); + if (mParcel.dataAvail() == 0) { + return false; + } + + int type = mParcel.readInt(); + if (type == KEY_LOCAL_SETTING) { + type = mParcel.readInt(); + if (type != KEY_START_TIME) { + return false; + } + int mStartTimeMs = mParcel.readInt(); + mKeyObjectMap.put(type, mStartTimeMs); + + type = mParcel.readInt(); + if (type != KEY_STRUCT_TEXT) { + return false; + } + + mTextStruct = new Text(); + mTextStruct.textLen = mParcel.readInt(); + + mTextStruct.text = mParcel.createByteArray(); + mKeyObjectMap.put(type, mTextStruct); + + } else if (type != KEY_GLOBAL_SETTING) { + Log.w(TAG, "Invalid timed text key found: " + type); + return false; + } + + while (mParcel.dataAvail() > 0) { + int key = mParcel.readInt(); + if (!isValidKey(key)) { + Log.w(TAG, "Invalid timed text key found: " + key); + return false; + } + + Object object = null; + + switch (key) { + case KEY_STRUCT_STYLE_LIST: { + readStyle(); + object = mStyleList; + break; + } + case KEY_STRUCT_FONT_LIST: { + readFont(); + object = mFontList; + break; + } + case KEY_STRUCT_HIGHLIGHT_LIST: { + readHighlight(); + object = mHighlightPosList; + break; + } + case KEY_STRUCT_KARAOKE_LIST: { + readKaraoke(); + object = mKaraokeList; + break; + } + case KEY_STRUCT_HYPER_TEXT_LIST: { + readHyperText(); + object = mHyperTextList; + + break; + } + case KEY_STRUCT_BLINKING_TEXT_LIST: { + readBlinkingText(); + object = mBlinkingPosList; + + break; + } + case KEY_WRAP_TEXT: { + mWrapText = mParcel.readInt(); + object = mWrapText; + break; + } + case KEY_HIGHLIGHT_COLOR_RGBA: { + mHighlightColorRGBA = mParcel.readInt(); + object = mHighlightColorRGBA; + break; + } + case KEY_DISPLAY_FLAGS: { + mDisplayFlags = mParcel.readInt(); + object = mDisplayFlags; + break; + } + case KEY_STRUCT_JUSTIFICATION: { + mJustification = new Justification(); + + mJustification.horizontalJustification = mParcel.readInt(); + mJustification.verticalJustification = mParcel.readInt(); + + object = mJustification; + break; + } + case KEY_BACKGROUND_COLOR_RGBA: { + mBackgroundColorRGBA = mParcel.readInt(); + object = mBackgroundColorRGBA; + break; + } + case KEY_STRUCT_TEXT_POS: { + mTextPos = new TextPos(); + + mTextPos.top = mParcel.readInt(); + mTextPos.left = mParcel.readInt(); + mTextPos.bottom = mParcel.readInt(); + mTextPos.right = mParcel.readInt(); + + object = mTextPos; + break; + } + case KEY_SCROLL_DELAY: { + mScrollDelay = mParcel.readInt(); + object = mScrollDelay; + break; + } + default: { + break; + } + } + + if (object != null) { + if (mKeyObjectMap.containsKey(key)) { + mKeyObjectMap.remove(key); + } + mKeyObjectMap.put(key, object); + } + } + + mParcel.recycle(); + return true; + } + + /** + * To parse and store the Style list. + */ + private void readStyle() { + Style style = new Style(); + boolean endOfStyle = false; + + while (!endOfStyle && (mParcel.dataAvail() > 0)) { + int key = mParcel.readInt(); + switch (key) { + case KEY_START_CHAR: { + style.startChar = mParcel.readInt(); + break; + } + case KEY_END_CHAR: { + style.endChar = mParcel.readInt(); + break; + } + case KEY_FONT_ID: { + style.fontID = mParcel.readInt(); + break; + } + case KEY_STYLE_FLAGS: { + int flags = mParcel.readInt(); + // In the absence of any bits set in flags, the text + // is plain. Otherwise, 1: bold, 2: italic, 4: underline + style.isBold = ((flags % 2) == 1); + style.isItalic = ((flags % 4) >= 2); + style.isUnderlined = ((flags / 4) == 1); + break; + } + case KEY_FONT_SIZE: { + style.fontSize = mParcel.readInt(); + break; + } + case KEY_TEXT_COLOR_RGBA: { + style.colorRGBA = mParcel.readInt(); + break; + } + default: { + // End of the Style parsing. Reset the data position back + // to the position before the last mParcel.readInt() call. + mParcel.setDataPosition(mParcel.dataPosition() - 4); + endOfStyle = true; + break; + } + } + } + + if (mStyleList == null) { + mStyleList = new ArrayList<Style>(); + } + mStyleList.add(style); + } + + /** + * To parse and store the Font list + */ + private void readFont() { + int entryCount = mParcel.readInt(); + + for (int i = 0; i < entryCount; i++) { + Font font = new Font(); + + font.ID = mParcel.readInt(); + int nameLen = mParcel.readInt(); + + byte[] text = mParcel.createByteArray(); + font.name = new String(text, 0, nameLen); + + if (mFontList == null) { + mFontList = new ArrayList<Font>(); + } + mFontList.add(font); + } + } + + /** + * To parse and store the Highlight list + */ + private void readHighlight() { + CharPos pos = new CharPos(); + + pos.startChar = mParcel.readInt(); + pos.endChar = mParcel.readInt(); + + if (mHighlightPosList == null) { + mHighlightPosList = new ArrayList<CharPos>(); + } + mHighlightPosList.add(pos); + } + + /** + * To parse and store the Karaoke list + */ + private void readKaraoke() { + int entryCount = mParcel.readInt(); + + for (int i = 0; i < entryCount; i++) { + Karaoke kara = new Karaoke(); + + kara.startTimeMs = mParcel.readInt(); + kara.endTimeMs = mParcel.readInt(); + kara.startChar = mParcel.readInt(); + kara.endChar = mParcel.readInt(); + + if (mKaraokeList == null) { + mKaraokeList = new ArrayList<Karaoke>(); + } + mKaraokeList.add(kara); + } + } + + /** + * To parse and store HyperText list + */ + private void readHyperText() { + HyperText hyperText = new HyperText(); + + hyperText.startChar = mParcel.readInt(); + hyperText.endChar = mParcel.readInt(); + + int len = mParcel.readInt(); + byte[] url = mParcel.createByteArray(); + hyperText.URL = new String(url, 0, len); + + len = mParcel.readInt(); + byte[] alt = mParcel.createByteArray(); + hyperText.altString = new String(alt, 0, len); + + if (mHyperTextList == null) { + mHyperTextList = new ArrayList<HyperText>(); + } + mHyperTextList.add(hyperText); + } + + /** + * To parse and store blinking text list + */ + private void readBlinkingText() { + CharPos blinkingPos = new CharPos(); + + blinkingPos.startChar = mParcel.readInt(); + blinkingPos.endChar = mParcel.readInt(); + + if (mBlinkingPosList == null) { + mBlinkingPosList = new ArrayList<CharPos>(); + } + mBlinkingPosList.add(blinkingPos); + } + + /** + * To check whether the given key is valid. + * @param key the key to be checked. + * @return true if the key is a valid one. Otherwise, false. + */ + public boolean isValidKey(final int key) { + if (!((key >= FIRST_PUBLIC_KEY) && (key <= LAST_PUBLIC_KEY)) + && !((key >= FIRST_PRIVATE_KEY) && (key <= LAST_PRIVATE_KEY))) { + return false; + } + return true; + } + + /** + * To check whether the given key is contained in this TimedText object. + * @param key the key to be checked. + * @return true if the key is contained in this TimedText object. + * Otherwise, false. + */ + public boolean containsKey(final int key) { + if (isValidKey(key) && mKeyObjectMap.containsKey(key)) { + return true; + } + return false; + } + /** + * @return a set of the keys contained in this TimedText object. + */ + public Set keySet() { + return mKeyObjectMap.keySet(); + } + + /** + * To retrieve the object associated with the key. Caller must make sure + * the key is present using the containsKey method otherwise a + * RuntimeException will occur. + * @param key the key used to retrieve the object. + * @return an object. The object could be an instanceof Integer, List, or + * any of the helper classes such as TextPos, Justification, and Text. + */ + public Object getObject(final int key) { + if (containsKey(key)) { + return mKeyObjectMap.get(key); + } else { + throw new IllegalArgumentException("Invalid key: " + key); + } + } +} diff --git a/media/java/android/media/videoeditor/VideoEditorImpl.java b/media/java/android/media/videoeditor/VideoEditorImpl.java index 2105deb1b9e2..649b98a6fa57 100755 --- a/media/java/android/media/videoeditor/VideoEditorImpl.java +++ b/media/java/android/media/videoeditor/VideoEditorImpl.java @@ -38,6 +38,7 @@ import android.graphics.Bitmap; import android.graphics.Rect; import android.media.videoeditor.MediaImageItem; import android.media.videoeditor.MediaItem; +import android.media.MediaMetadataRetriever; import android.util.Log; import android.util.Xml; import android.view.Surface; @@ -1833,12 +1834,32 @@ public class VideoEditorImpl implements VideoEditor { } Bitmap projectBitmap = null; - try { - projectBitmap = mI.getThumbnail(width, height, 500); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException ("Illegal argument error creating project thumbnail"); - } catch (IOException e) { - throw new IllegalArgumentException ("IO Error creating project thumbnail"); + String filename = mI.getFilename(); + if (mI instanceof MediaVideoItem) { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(filename); + Bitmap bitmap = retriever.getFrameAtTime(); + retriever.release(); + retriever = null; + if (bitmap == null) { + String msg = "Thumbnail extraction from " + + filename + " failed"; + throw new IllegalArgumentException(msg); + } + // Resize the thumbnail to the target size + projectBitmap = + Bitmap.createScaledBitmap(bitmap, width, height, true); + } else { + try { + projectBitmap = mI.getThumbnail(width, height, 500); + } catch (IllegalArgumentException e) { + String msg = "Project thumbnail extraction from " + + filename + " failed"; + throw new IllegalArgumentException(msg); + } catch (IOException e) { + String msg = "IO Error creating project thumbnail"; + throw new IllegalArgumentException(msg); + } } try { diff --git a/media/jni/mediaeditor/VideoEditorClasses.cpp b/media/jni/mediaeditor/VideoEditorClasses.cpp index 5696433614fe..d43a5622a769 100755 --- a/media/jni/mediaeditor/VideoEditorClasses.cpp +++ b/media/jni/mediaeditor/VideoEditorClasses.cpp @@ -144,6 +144,7 @@ VIDEOEDIT_JAVA_DEFINE_CONSTANTS(ClipType) VIDEOEDIT_JAVA_CONSTANT_INIT("MP3", M4VIDEOEDITING_kFileType_MP3), VIDEOEDIT_JAVA_CONSTANT_INIT("PCM", M4VIDEOEDITING_kFileType_PCM), VIDEOEDIT_JAVA_CONSTANT_INIT("JPG", M4VIDEOEDITING_kFileType_JPG), + VIDEOEDIT_JAVA_CONSTANT_INIT("PNG", M4VIDEOEDITING_kFileType_PNG), VIDEOEDIT_JAVA_CONSTANT_INIT("M4V", M4VIDEOEDITING_kFileType_M4V), VIDEOEDIT_JAVA_CONSTANT_INIT("UNSUPPORTED", M4VIDEOEDITING_kFileType_Unsupported) }; @@ -1394,8 +1395,8 @@ videoEditClasses_getClipSettings( pSettings->FileType = (M4VIDEOEDITING_FileType)videoEditJava_getClipTypeJavaToC( &converted, pEnv->GetIntField(object, fieldIds.fileType)); - if ( pSettings->FileType == M4VIDEOEDITING_kFileType_JPG) - { + if (( pSettings->FileType == M4VIDEOEDITING_kFileType_JPG) || + ( pSettings->FileType == M4VIDEOEDITING_kFileType_PNG)) { pSettings->FileType = M4VIDEOEDITING_kFileType_ARGB8888; } diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp index a41d7abd4aeb..4c4aad0ababf 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -70,7 +70,7 @@ status_t AudioRecord::getMinFrameCount( size <<= 1; if (audio_is_linear_pcm(format)) { - size /= channelCount * (format == AUDIO_FORMAT_PCM_16_BIT ? 2 : 1); + size /= channelCount * audio_bytes_per_sample(format); } *frameCount = size; @@ -258,7 +258,7 @@ uint32_t AudioRecord::frameCount() const int AudioRecord::frameSize() const { if (audio_is_linear_pcm(mFormat)) { - return channelCount()*((format() == AUDIO_FORMAT_PCM_8_BIT) ? sizeof(uint8_t) : sizeof(int16_t)); + return channelCount()*audio_bytes_per_sample(mFormat); } else { return sizeof(uint8_t); } @@ -481,7 +481,7 @@ status_t AudioRecord::obtainBuffer(Buffer* audioBuffer, int32_t waitCount) { AutoMutex lock(mLock); int active; - status_t result; + status_t result = NO_ERROR; audio_track_cblk_t* cblk = mCblk; uint32_t framesReq = audioBuffer->frameCount; uint32_t waitTimeMs = (waitCount < 0) ? cblk->bufferTimeoutMs : WAIT_PERIOD_MS; diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp index 37fe1823b74c..31eb97a968a7 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -298,7 +298,7 @@ uint32_t AudioTrack::frameCount() const int AudioTrack::frameSize() const { if (audio_is_linear_pcm(mFormat)) { - return channelCount()*((format() == AUDIO_FORMAT_PCM_8_BIT) ? sizeof(uint8_t) : sizeof(int16_t)); + return channelCount()*audio_bytes_per_sample(mFormat); } else { return sizeof(uint8_t); } @@ -314,7 +314,7 @@ sp<IMemory>& AudioTrack::sharedBuffer() void AudioTrack::start() { sp<AudioTrackThread> t = mAudioTrackThread; - status_t status; + status_t status = NO_ERROR; LOGV("start %p", this); if (t != 0) { @@ -825,7 +825,7 @@ status_t AudioTrack::obtainBuffer(Buffer* audioBuffer, int32_t waitCount) { AutoMutex lock(mLock); int active; - status_t result; + status_t result = NO_ERROR; audio_track_cblk_t* cblk = mCblk; uint32_t framesReq = audioBuffer->frameCount; uint32_t waitTimeMs = (waitCount < 0) ? cblk->bufferTimeoutMs : WAIT_PERIOD_MS; diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 5fe511fbb543..5582f9278a64 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -889,11 +889,17 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { uint32_t entry_count = U32_AT(&buffer[4]); if (entry_count > 1) { - // For now we only support a single type of media per track. - - mLastTrack->skipTrack = true; - *offset += chunk_size; - break; + // For 3GPP timed text, there could be multiple tx3g boxes contain + // multiple text display formats. These formats will be used to + // display the timed text. + const char *mime; + CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime)); + if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + // For now we only support a single type of media per track. + mLastTrack->skipTrack = true; + *offset += chunk_size; + break; + } } off64_t stop_offset = *offset + chunk_size; @@ -1324,9 +1330,53 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { return parseDrmSINF(offset, data_offset); } + case FOURCC('h', 'd', 'l', 'r'): + { + uint32_t buffer; + if (mDataSource->readAt( + data_offset + 8, &buffer, 4) < 4) { + return ERROR_IO; + } + + uint32_t type = ntohl(buffer); + // For the 3GPP file format, the handler-type within the 'hdlr' box + // shall be 'text' + if (type == FOURCC('t', 'e', 'x', 't')) { + mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP); + } + + *offset += chunk_size; + break; + } + case FOURCC('t', 'x', '3', 'g'): { - mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP); + uint32_t type; + const void *data; + size_t size = 0; + if (!mLastTrack->meta->findData( + kKeyTextFormatData, &type, &data, &size)) { + size = 0; + } + + uint8_t *buffer = new uint8_t[size + chunk_size]; + + if (size > 0) { + memcpy(buffer, data, size); + } + + if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size)) + < chunk_size) { + delete[] buffer; + buffer = NULL; + + return ERROR_IO; + } + + mLastTrack->meta->setData( + kKeyTextFormatData, 0, buffer, size + chunk_size); + + delete[] buffer; *offset += chunk_size; break; diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp index ea9911cdee17..f075699d6a47 100644 --- a/media/libstagefright/MPEG4Writer.cpp +++ b/media/libstagefright/MPEG4Writer.cpp @@ -205,6 +205,8 @@ private: // Duration is time scale based void addOneSttsTableEntry(size_t sampleCount, int32_t timescaledDur); void addOneCttsTableEntry(size_t sampleCount, int32_t timescaledDur); + + bool isTrackMalFormed() const; void sendTrackSummary(bool hasMultipleTracks); // Write the boxes @@ -1975,7 +1977,6 @@ status_t MPEG4Writer::Track::threadEntry() { } CHECK(timestampUs >= 0); - LOGV("%s media time stamp: %lld and previous paused duration %lld", mIsAudio? "Audio": "Video", timestampUs, previousPausedDurationUs); if (timestampUs > mTrackDurationUs) { @@ -2082,11 +2083,10 @@ status_t MPEG4Writer::Track::threadEntry() { } - if (mSampleSizes.empty() || // no samples written - (!mIsAudio && mNumStssTableEntries == 0) || // no sync frames for video - (OK != checkCodecSpecificData())) { // no codec specific data + if (isTrackMalFormed()) { err = ERROR_MALFORMED; } + mOwner->trackProgressStatus(mTrackId, -1, err); // Last chunk @@ -2136,6 +2136,24 @@ status_t MPEG4Writer::Track::threadEntry() { return err; } +bool MPEG4Writer::Track::isTrackMalFormed() const { + if (mSampleSizes.empty()) { // no samples written + LOGE("The number of recorded samples is 0"); + return true; + } + + if (!mIsAudio && mNumStssTableEntries == 0) { // no sync frames for video + LOGE("There are no sync frames for video track"); + return true; + } + + if (OK != checkCodecSpecificData()) { // no codec specific data + return true; + } + + return false; +} + void MPEG4Writer::Track::sendTrackSummary(bool hasMultipleTracks) { // Send track summary only if test mode is enabled. diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp index f03f7a268fb2..fd0505eb267c 100644 --- a/media/libstagefright/rtsp/ASessionDescription.cpp +++ b/media/libstagefright/rtsp/ASessionDescription.cpp @@ -301,6 +301,9 @@ void ASessionDescription::ParseFormatDesc( // static bool ASessionDescription::parseNTPRange( const char *s, float *npt1, float *npt2) { + *npt1 = 0.0f; + *npt2 = 0.0f; + if (s[0] == '-') { return false; // no start time available. } diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h index d15d9c5c0966..f89f8e20054d 100644 --- a/media/libstagefright/rtsp/MyHandler.h +++ b/media/libstagefright/rtsp/MyHandler.h @@ -995,10 +995,12 @@ struct MyHandler : public AHandler { AString val; CHECK(GetAttribute(range.c_str(), "npt", &val)); + bool seekable = true; + float npt1, npt2; if (!ASessionDescription::parseNTPRange(val.c_str(), &npt1, &npt2)) { // This is a live stream and therefore not seekable. - return; + seekable = false; } i = response->mHeaders.indexOfKey("rtp-info"); @@ -1044,7 +1046,7 @@ struct MyHandler : public AHandler { ++n; } - mSeekable = true; + mSeekable = seekable; } sp<APacketSource> getPacketSource(size_t index) { diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk index 9a6062cdd608..59d0e154c597 100644 --- a/media/libstagefright/timedtext/Android.mk +++ b/media/libstagefright/timedtext/Android.mk @@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + TextDescriptions.cpp \ TimedTextParser.cpp \ TimedTextPlayer.cpp diff --git a/media/libstagefright/timedtext/TextDescriptions.cpp b/media/libstagefright/timedtext/TextDescriptions.cpp new file mode 100644 index 000000000000..f9c1fe093803 --- /dev/null +++ b/media/libstagefright/timedtext/TextDescriptions.cpp @@ -0,0 +1,385 @@ +/* + * 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. + */ + +#include "TextDescriptions.h" +#include <media/stagefright/Utils.h> +#include <media/stagefright/MediaErrors.h> + +namespace android { + +TextDescriptions::TextDescriptions() { +} + +status_t TextDescriptions::getParcelOfDescriptions( + const uint8_t *data, ssize_t size, + uint32_t flags, int timeMs, Parcel *parcel) { + parcel->freeData(); + + if (flags & IN_BAND_TEXT_3GPP) { + if (flags & GLOBAL_DESCRIPTIONS) { + return extract3GPPGlobalDescriptions(data, size, parcel, 0); + } else if (flags & LOCAL_DESCRIPTIONS) { + return extract3GPPLocalDescriptions(data, size, timeMs, parcel, 0); + } + } else if (flags & OUT_OF_BAND_TEXT_SRT) { + if (flags & LOCAL_DESCRIPTIONS) { + return extractSRTLocalDescriptions(data, size, timeMs, parcel); + } + } + + return ERROR_UNSUPPORTED; +} + +// Parse the SRT text sample, and store the timing and text sample in a Parcel. +// The Parcel will be sent to MediaPlayer.java through event, and will be +// parsed in TimedText.java. +status_t TextDescriptions::extractSRTLocalDescriptions( + const uint8_t *data, ssize_t size, int timeMs, Parcel *parcel) { + parcel->writeInt32(KEY_LOCAL_SETTING); + parcel->writeInt32(KEY_START_TIME); + parcel->writeInt32(timeMs); + + parcel->writeInt32(KEY_STRUCT_TEXT); + // write the size of the text sample + parcel->writeInt32(size); + // write the text sample as a byte array + parcel->writeInt32(size); + parcel->write(data, size); + + return OK; +} + +// Extract the local 3GPP display descriptions. 3GPP local descriptions +// are appended to the text sample if any. The descriptions could include +// information such as text styles, highlights, karaoke and so on. They +// are contained in different boxes, such as 'styl' box contains text +// styles, and 'krok' box contains karaoke timing and positions. +status_t TextDescriptions::extract3GPPLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel, int depth) { + if (depth == 0) { + parcel->writeInt32(KEY_LOCAL_SETTING); + + // write start time to display this text sample + parcel->writeInt32(KEY_START_TIME); + parcel->writeInt32(timeMs); + + ssize_t textLen = (*data) << 8 | (*(data + 1)); + + // write text sample length and text sample itself + parcel->writeInt32(KEY_STRUCT_TEXT); + parcel->writeInt32(textLen); + parcel->writeInt32(textLen); + parcel->write(data + 2, textLen); + + if (size > textLen) { + data += (textLen + 2); + size -= (textLen + 2); + } else { + return OK; + } + } + + const uint8_t *tmpData = data; + ssize_t chunkSize = U32_AT(tmpData); + uint32_t chunkType = U32_AT(tmpData + 4); + + if (chunkSize <= 0) { + return OK; + } + + tmpData += 8; + + switch(chunkType) { + // 'styl' box specifies the style of the text. + case FOURCC('s', 't', 'y', 'l'): + { + uint16_t count = U16_AT(tmpData); + + tmpData += 2; + + for (int i = 0; i < count; i++) { + parcel->writeInt32(KEY_STRUCT_STYLE_LIST); + parcel->writeInt32(KEY_START_CHAR); + parcel->writeInt32(U16_AT(tmpData)); + + parcel->writeInt32(KEY_END_CHAR); + parcel->writeInt32(U16_AT(tmpData + 2)); + + parcel->writeInt32(KEY_FONT_ID); + parcel->writeInt32(U16_AT(tmpData + 4)); + + parcel->writeInt32(KEY_STYLE_FLAGS); + parcel->writeInt32(*(tmpData + 6)); + + parcel->writeInt32(KEY_FONT_SIZE); + parcel->writeInt32(*(tmpData + 7)); + + parcel->writeInt32(KEY_TEXT_COLOR_RGBA); + uint32_t rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 + | *(tmpData + 10) << 8 | *(tmpData + 11); + parcel->writeInt32(rgba); + + tmpData += 12; + } + + break; + } + // 'krok' box. The number of highlight events is specified, and each + // event is specified by a starting and ending char offset and an end + // time for the event. + case FOURCC('k', 'r', 'o', 'k'): + { + + parcel->writeInt32(KEY_STRUCT_KARAOKE_LIST); + + int startTime = U32_AT(tmpData); + uint16_t count = U16_AT(tmpData + 4); + parcel->writeInt32(count); + + tmpData += 6; + int lastEndTime = 0; + + for (int i = 0; i < count; i++) { + parcel->writeInt32(startTime + lastEndTime); + + lastEndTime = U32_AT(tmpData); + parcel->writeInt32(lastEndTime); + + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + tmpData += 8; + } + + break; + } + // 'hlit' box specifies highlighted text + case FOURCC('h', 'l', 'i', 't'): + { + parcel->writeInt32(KEY_STRUCT_HIGHLIGHT_LIST); + + // the start char offset to highlight + parcel->writeInt32(U16_AT(tmpData)); + // the last char offset to highlight + parcel->writeInt32(U16_AT(tmpData + 2)); + + break; + } + // 'hclr' box specifies the RGBA color: 8 bits each of + // red, green, blue, and an alpha(transparency) value + case FOURCC('h', 'c', 'l', 'r'): + { + parcel->writeInt32(KEY_HIGHLIGHT_COLOR_RGBA); + + uint32_t rgba = *(tmpData) << 24 | *(tmpData + 1) << 16 + | *(tmpData + 2) << 8 | *(tmpData + 3); + parcel->writeInt32(rgba); + + break; + } + // 'dlay' box specifies a delay after a scroll in and/or + // before scroll out. + case FOURCC('d', 'l', 'a', 'y'): + { + parcel->writeInt32(KEY_SCROLL_DELAY); + + uint32_t delay = *(tmpData) << 24 | *(tmpData + 1) << 16 + | *(tmpData + 2) << 8 | *(tmpData + 3); + parcel->writeInt32(delay); + + break; + } + // 'href' box for hyper text link + case FOURCC('h', 'r', 'e', 'f'): + { + parcel->writeInt32(KEY_STRUCT_HYPER_TEXT_LIST); + + // the start offset of the text to be linked + parcel->writeInt32(U16_AT(tmpData)); + // the end offset of the text + parcel->writeInt32(U16_AT(tmpData + 2)); + + // the number of bytes in the following URL + int len = *(tmpData + 4); + parcel->writeInt32(len); + + // the linked-to URL + parcel->writeInt32(len); + parcel->write(tmpData + 5, len); + + tmpData += (5 + len); + + // the number of bytes in the following "alt" string + len = *tmpData; + parcel->writeInt32(len); + + // an "alt" string for user display + parcel->writeInt32(len); + parcel->write(tmpData + 1, len); + + break; + } + // 'tbox' box to indicate the position of the text with values + // of top, left, bottom and right + case FOURCC('t', 'b', 'o', 'x'): + { + parcel->writeInt32(KEY_STRUCT_TEXT_POS); + parcel->writeInt32(U16_AT(tmpData)); + parcel->writeInt32(U16_AT(tmpData + 2)); + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + break; + } + // 'blnk' to specify the char range to be blinked + case FOURCC('b', 'l', 'n', 'k'): + { + parcel->writeInt32(KEY_STRUCT_BLINKING_TEXT_LIST); + + // start char offset + parcel->writeInt32(U16_AT(tmpData)); + // end char offset + parcel->writeInt32(U16_AT(tmpData + 2)); + + break; + } + // 'twrp' box specifies text wrap behavior. If the value if 0x00, + // then no wrap. If it's 0x01, then automatic 'soft' wrap is enabled. + // 0x02-0xff are reserved. + case FOURCC('t', 'w', 'r', 'p'): + { + parcel->writeInt32(KEY_WRAP_TEXT); + parcel->writeInt32(*tmpData); + + break; + } + default: + { + break; + } + } + + if (size > chunkSize) { + data += chunkSize; + size -= chunkSize; + // continue to parse next box + return extract3GPPLocalDescriptions(data, size, 0, parcel, 1); + } + + return OK; +} + +// To extract box 'tx3g' defined in 3GPP TS 26.245, and store it in a Parcel +status_t TextDescriptions::extract3GPPGlobalDescriptions( + const uint8_t *data, ssize_t size, Parcel *parcel, int depth) { + + ssize_t chunkSize = U32_AT(data); + uint32_t chunkType = U32_AT(data + 4); + const uint8_t *tmpData = data; + tmpData += 8; + + if (size < chunkSize) { + return OK; + } + + if (depth == 0) { + parcel->writeInt32(KEY_GLOBAL_SETTING); + } + switch(chunkType) { + case FOURCC('t', 'x', '3', 'g'): + { + tmpData += 8; // skip the first 8 bytes + parcel->writeInt32(KEY_DISPLAY_FLAGS); + parcel->writeInt32(U32_AT(tmpData)); + + parcel->writeInt32(KEY_STRUCT_JUSTIFICATION); + parcel->writeInt32(tmpData[4]); + parcel->writeInt32(tmpData[5]); + + parcel->writeInt32(KEY_BACKGROUND_COLOR_RGBA); + uint32_t rgba = *(tmpData + 6) << 24 | *(tmpData + 7) << 16 + | *(tmpData + 8) << 8 | *(tmpData + 9); + parcel->writeInt32(rgba); + + tmpData += 10; + parcel->writeInt32(KEY_STRUCT_TEXT_POS); + parcel->writeInt32(U16_AT(tmpData)); + parcel->writeInt32(U16_AT(tmpData + 2)); + parcel->writeInt32(U16_AT(tmpData + 4)); + parcel->writeInt32(U16_AT(tmpData + 6)); + + tmpData += 8; + parcel->writeInt32(KEY_STRUCT_STYLE_LIST); + parcel->writeInt32(KEY_START_CHAR); + parcel->writeInt32(U16_AT(tmpData)); + + parcel->writeInt32(KEY_END_CHAR); + parcel->writeInt32(U16_AT(tmpData + 2)); + + parcel->writeInt32(KEY_FONT_ID); + parcel->writeInt32(U16_AT(tmpData + 4)); + + parcel->writeInt32(KEY_STYLE_FLAGS); + parcel->writeInt32(*(tmpData + 6)); + + parcel->writeInt32(KEY_FONT_SIZE); + parcel->writeInt32(*(tmpData + 7)); + + parcel->writeInt32(KEY_TEXT_COLOR_RGBA); + rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16 + | *(tmpData + 10) << 8 | *(tmpData + 11); + parcel->writeInt32(rgba); + + tmpData += 12; + parcel->writeInt32(KEY_STRUCT_FONT_LIST); + uint16_t count = U16_AT(tmpData); + parcel->writeInt32(count); + + tmpData += 2; + for (int i = 0; i < count; i++) { + // font ID + parcel->writeInt32(U16_AT(tmpData)); + + // font name length + parcel->writeInt32(*(tmpData + 2)); + + int len = *(tmpData + 2); + + parcel->write(tmpData + 3, len); + tmpData += 3 + len; + } + + break; + } + default: + { + break; + } + } + + data += chunkSize; + size -= chunkSize; + + if (size > 0) { + // continue to extract next 'tx3g' + return extract3GPPGlobalDescriptions(data, size, parcel, 1); + } + + return OK; +} + +} // namespace android diff --git a/media/libstagefright/timedtext/TextDescriptions.h b/media/libstagefright/timedtext/TextDescriptions.h new file mode 100644 index 000000000000..0144917505e9 --- /dev/null +++ b/media/libstagefright/timedtext/TextDescriptions.h @@ -0,0 +1,84 @@ + /* + * 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. + */ + +#ifndef TEXT_DESCRIPTIONS_H_ + +#define TEXT_DESCRIPTIONS_H_ + +#include <binder/Parcel.h> +#include <media/stagefright/foundation/ABase.h> + +namespace android { + +class TextDescriptions { +public: + enum { + IN_BAND_TEXT_3GPP = 0x01, + OUT_OF_BAND_TEXT_SRT = 0x02, + + GLOBAL_DESCRIPTIONS = 0x100, + LOCAL_DESCRIPTIONS = 0x200, + }; + + static status_t getParcelOfDescriptions( + const uint8_t *data, ssize_t size, + uint32_t flags, int timeMs, Parcel *parcel); +private: + TextDescriptions(); + + enum { + // These keys must be in sync with the keys in TimedText.java + KEY_DISPLAY_FLAGS = 1, // int + KEY_STYLE_FLAGS = 2, // int + KEY_BACKGROUND_COLOR_RGBA = 3, // int + KEY_HIGHLIGHT_COLOR_RGBA = 4, // int + KEY_SCROLL_DELAY = 5, // int + KEY_WRAP_TEXT = 6, // int + KEY_START_TIME = 7, // int + KEY_STRUCT_BLINKING_TEXT_LIST = 8, // List<CharPos> + KEY_STRUCT_FONT_LIST = 9, // List<Font> + KEY_STRUCT_HIGHLIGHT_LIST = 10, // List<CharPos> + KEY_STRUCT_HYPER_TEXT_LIST = 11, // List<HyperText> + KEY_STRUCT_KARAOKE_LIST = 12, // List<Karaoke> + KEY_STRUCT_STYLE_LIST = 13, // List<Style> + KEY_STRUCT_TEXT_POS = 14, // TextPos + KEY_STRUCT_JUSTIFICATION = 15, // Justification + KEY_STRUCT_TEXT = 16, // Text + + KEY_GLOBAL_SETTING = 101, + KEY_LOCAL_SETTING = 102, + KEY_START_CHAR = 103, + KEY_END_CHAR = 104, + KEY_FONT_ID = 105, + KEY_FONT_SIZE = 106, + KEY_TEXT_COLOR_RGBA = 107, + }; + + static status_t extractSRTLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel); + static status_t extract3GPPGlobalDescriptions( + const uint8_t *data, ssize_t size, + Parcel *parcel, int depth); + static status_t extract3GPPLocalDescriptions( + const uint8_t *data, ssize_t size, + int timeMs, Parcel *parcel, int depth); + + DISALLOW_EVIL_CONSTRUCTORS(TextDescriptions); +}; + +} // namespace android +#endif // TEXT_DESCRIPTIONS_H_ diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp index 50bb16d3f2a9..7c8a747c518f 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.cpp +++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp @@ -19,6 +19,7 @@ #include <utils/Log.h> #include <binder/IPCThreadState.h> + #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> @@ -27,9 +28,11 @@ #include <media/stagefright/MediaBuffer.h> #include <media/stagefright/FileSource.h> #include <media/stagefright/Utils.h> + #include "include/AwesomePlayer.h" #include "TimedTextPlayer.h" #include "TimedTextParser.h" +#include "TextDescriptions.h" namespace android { @@ -92,10 +95,11 @@ status_t TimedTextPlayer::start(uint8_t index) { return BAD_VALUE; } + status_t err; if (index < mTextTrackVector.size()) { // start an in-band text mSource = mTextTrackVector.itemAt(index); - status_t err = mSource->start(); + err = mSource->start(); if (err != OK) { return err; @@ -112,13 +116,17 @@ status_t TimedTextPlayer::start(uint8_t index) { mTextParser = new TimedTextParser(); } - status_t err; if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) { return err; } mTextType = kOutOfBandText; } + // send sample description format + if ((err = extractAndSendGlobalDescriptions()) != OK) { + return err; + } + int64_t positionUs; mObserver->getPosition(&positionUs); seekTo(positionUs); @@ -211,21 +219,17 @@ void TimedTextPlayer::onTextEvent() { } mTextEventPending = false; + if (mData.dataSize() > 0) { + notifyListener(MEDIA_TIMED_TEXT, &mData); + mData.freeData(); + } + MediaSource::ReadOptions options; if (mSeeking) { options.setSeekTo(mSeekTimeUs, MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC); mSeeking = false; - if (mTextType == kInBandText) { - if (mTextBuffer != NULL) { - mTextBuffer->release(); - mTextBuffer = NULL; - } - } else { - mText.clear(); - } - notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen } @@ -233,32 +237,12 @@ void TimedTextPlayer::onTextEvent() { mObserver->getPosition(&positionUs); if (mTextType == kInBandText) { - if (mTextBuffer != NULL) { - uint8_t *tmp = (uint8_t *)(mTextBuffer->data()); - size_t len = (*tmp) << 8 | (*(tmp + 1)); - - notifyListener(MEDIA_TIMED_TEXT, - tmp + 2, - len); - - mTextBuffer->release(); - mTextBuffer = NULL; - - } - if (mSource->read(&mTextBuffer, &options) != OK) { return; } mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs); } else { - if (mText.size() > 0) { - notifyListener(MEDIA_TIMED_TEXT, - mText.c_str(), - mText.size()); - mText.clear(); - } - int64_t endTimeUs; if (mTextParser->getText( &mText, &timeUs, &endTimeUs, &options) != OK) { @@ -266,6 +250,19 @@ void TimedTextPlayer::onTextEvent() { } } + if (timeUs > 0) { + extractAndAppendLocalDescriptions(timeUs); + } + + if (mTextType == kInBandText) { + if (mTextBuffer != NULL) { + mTextBuffer->release(); + mTextBuffer = NULL; + } + } else { + mText.clear(); + } + //send the text now if (timeUs <= positionUs + 100000ll) { postTextEvent(); @@ -297,7 +294,8 @@ status_t TimedTextPlayer::setParameter(int key, const Parcel &request) { Mutex::Autolock autoLock(mLock); if (key == KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE) { - String8 uri = request.readString8(); + const String16 uri16 = request.readString16(); + String8 uri = String8(uri16); KeyedVector<String8, String8> headers; // To support local subtitle file only for now @@ -327,21 +325,92 @@ status_t TimedTextPlayer::setParameter(int key, const Parcel &request) { return INVALID_OPERATION; } -void TimedTextPlayer::notifyListener( - int msg, const void *data, size_t size) { +void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) { if (mListener != NULL) { sp<MediaPlayerBase> listener = mListener.promote(); if (listener != NULL) { - if (size > 0) { - mData.freeData(); - mData.write(data, size); - - listener->sendEvent(msg, 0, 0, &mData); + if (parcel && (parcel->dataSize() > 0)) { + listener->sendEvent(msg, 0, 0, parcel); } else { // send an empty timed text to clear the screen listener->sendEvent(msg); } } } } + +// Each text sample consists of a string of text, optionally with sample +// modifier description. The modifier description could specify a new +// text style for the string of text. These descriptions are present only +// if they are needed. This method is used to extract the modifier +// description and append it at the end of the text. +status_t TimedTextPlayer::extractAndAppendLocalDescriptions(int64_t timeUs) { + const void *data; + size_t size = 0; + int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS; + + if (mTextType == kInBandText) { + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + data = mTextBuffer->data(); + size = mTextBuffer->size(); + } else { + // support 3GPP only for now + return ERROR_UNSUPPORTED; + } + } else { + data = mText.c_str(); + size = mText.size(); + flag |= TextDescriptions::OUT_OF_BAND_TEXT_SRT; + } + + if ((size > 0) && (flag != TextDescriptions::LOCAL_DESCRIPTIONS)) { + mData.freeData(); + return TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, timeUs / 1000, &mData); + } + + return OK; +} + +// To extract and send the global text descriptions for all the text samples +// in the text track or text file. +status_t TimedTextPlayer::extractAndSendGlobalDescriptions() { + const void *data; + size_t size = 0; + int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS; + + if (mTextType == kInBandText) { + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + // support 3GPP only for now + if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) { + uint32_t type; + // get the 'tx3g' box content. This box contains the text descriptions + // used to render the text track + if (!mSource->getFormat()->findData( + kKeyTextFormatData, &type, &data, &size)) { + return ERROR_MALFORMED; + } + + flag |= TextDescriptions::IN_BAND_TEXT_3GPP; + } + } + + if ((size > 0) && (flag != TextDescriptions::GLOBAL_DESCRIPTIONS)) { + Parcel parcel; + if (TextDescriptions::getParcelOfDescriptions( + (const uint8_t *)data, size, flag, 0, &parcel) == OK) { + if (parcel.dataSize() > 0) { + notifyListener(MEDIA_TIMED_TEXT, &parcel); + } + } + } + + return OK; +} } diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h index 590760b10977..a744db59417b 100644 --- a/media/libstagefright/timedtext/TimedTextPlayer.h +++ b/media/libstagefright/timedtext/TimedTextPlayer.h @@ -103,8 +103,10 @@ private: void postTextEvent(int64_t delayUs = -1); void cancelTextEvent(); - void notifyListener( - int msg, const void *data = NULL, size_t size = 0); + void notifyListener(int msg, const Parcel *parcel = NULL); + + status_t extractAndAppendLocalDescriptions(int64_t timeUs); + status_t extractAndSendGlobalDescriptions(); DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer); }; diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java index 7775dbf9167a..e0c38b12229b 100644 --- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java +++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java @@ -18,7 +18,6 @@ package com.android.nfc_extras; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.nfc.ApduList; import android.nfc.INfcAdapterExtras; import android.nfc.NfcAdapter; import android.os.RemoteException; @@ -207,4 +206,21 @@ public final class NfcAdapterExtras { public NfcExecutionEnvironment getEmbeddedExecutionEnvironment() { return sEmbeddedEe; } + + /** + * Authenticate the client application. + * + * Some implementations of NFC Adapter Extras may require applications + * to authenticate with a token, before using other methods. + * + * @param a implementation specific token + * @throws a {@link java.lang.SecurityException} if authentication failed + */ + public void authenticate(byte[] token) { + try { + sService.authenticate(token); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index 93f7af3173fb..fe279c838b61 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -1040,7 +1040,7 @@ public class TabletStatusBar extends StatusBar implements } }); - WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams( + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG, diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java index 187a486f8ce2..60906a1e08dd 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java @@ -1,93 +1,96 @@ -/*
- * 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.systemui.usb;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.hardware.usb.UsbManager;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.util.Log;
-import android.widget.Button;
-
-import java.io.File;
-
-import com.android.systemui.R;
-
-public class UsbPreferenceActivity extends Activity implements View.OnClickListener {
-
- private static final String TAG = "UsbPreferenceActivity";
-
- private UsbManager mUsbManager;
- private String mCurrentFunction;
- private String[] mFunctions;
- private String mInstallerImagePath;
- private Button mMtpPtpButton;
- private Button mInstallerCdButton;
- private boolean mPtpActive;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
-
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
- dialogBuilder.setTitle(getString(R.string.usb_preference_title));
-
- LayoutInflater inflater = (LayoutInflater)getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- View buttonView = inflater.inflate(R.layout.usb_preference_buttons, null);
- dialogBuilder.setView(buttonView);
- mMtpPtpButton = (Button)buttonView.findViewById(R.id.mtp_ptp_button);
- mInstallerCdButton = (Button)buttonView.findViewById(R.id.installer_cd_button);
- mMtpPtpButton.setOnClickListener(this);
- mInstallerCdButton.setOnClickListener(this);
-
- mPtpActive = mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP);
- if (mPtpActive) {
- mMtpPtpButton.setText(R.string.use_mtp_button_title);
- }
-
- mInstallerImagePath = getString(com.android.internal.R.string.config_isoImagePath);
- if (!(new File(mInstallerImagePath)).exists()) {
- mInstallerCdButton.setVisibility(View.GONE);
- }
-
- dialogBuilder.show();
- }
-
- public void onClick(View v) {
- if (v.equals(mMtpPtpButton)) {
- if (mPtpActive) {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_MTP);
- mUsbManager.setDefaultFunction(UsbManager.USB_FUNCTION_MTP);
- } else {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_PTP);
- mUsbManager.setDefaultFunction(UsbManager.USB_FUNCTION_PTP);
- }
- } else if (v.equals(mInstallerCdButton)) {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_MASS_STORAGE);
- mUsbManager.setMassStorageBackingFile(mInstallerImagePath);
- }
-
+/* + * 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.systemui.usb; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.util.Log; +import android.widget.Button; + +import java.io.File; + +import com.android.systemui.R; + +public class UsbPreferenceActivity extends Activity implements View.OnClickListener { + + private static final String TAG = "UsbPreferenceActivity"; + + private UsbManager mUsbManager; + private String mCurrentFunction; + private String[] mFunctions; + private String mInstallerImagePath; + private AlertDialog mDialog; + private Button mMtpPtpButton; + private Button mInstallerCdButton; + private boolean mPtpActive; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE); + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + dialogBuilder.setTitle(getString(R.string.usb_preference_title)); + + LayoutInflater inflater = (LayoutInflater)getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + View buttonView = inflater.inflate(R.layout.usb_preference_buttons, null); + dialogBuilder.setView(buttonView); + mMtpPtpButton = (Button)buttonView.findViewById(R.id.mtp_ptp_button); + mInstallerCdButton = (Button)buttonView.findViewById(R.id.installer_cd_button); + mMtpPtpButton.setOnClickListener(this); + mInstallerCdButton.setOnClickListener(this); + + mPtpActive = mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP); + if (mPtpActive) { + mMtpPtpButton.setText(R.string.use_mtp_button_title); + } + + mInstallerImagePath = getString(com.android.internal.R.string.config_isoImagePath); + if (!(new File(mInstallerImagePath)).exists()) { + mInstallerCdButton.setVisibility(View.GONE); + } + + mDialog = dialogBuilder.show(); + } + + public void onClick(View v) { + if (v.equals(mMtpPtpButton)) { + if (mPtpActive) { + mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true); + } else { + mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true); + } + } else if (v.equals(mInstallerCdButton)) { + // installer CD is never default + mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MASS_STORAGE, false); + mUsbManager.setMassStorageBackingFile(mInstallerImagePath); + } + + if (mDialog != null) { + mDialog.dismiss(); + } finish();
- }
-}
+ } +} diff --git a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java index c4feefd2e32c..de4ea75aec0a 100644 --- a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java @@ -110,7 +110,7 @@ public class AccountUnlockScreen extends RelativeLayout implements KeyguardScree mOk = (Button) findViewById(R.id.ok); mOk.setOnClickListener(this); - mEmergencyCall = (Button) findViewById(R.id.emergencyCall); + mEmergencyCall = (Button) findViewById(R.id.emergencyCallButton); mEmergencyCall.setOnClickListener(this); mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCall); diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java index 6734005a3256..f862d01e3ecb 100644 --- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java @@ -129,7 +129,7 @@ public class PasswordUnlockScreen extends LinearLayout implements KeyguardScreen | InputType.TYPE_TEXT_VARIATION_PASSWORD); } - mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall); + mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton); mEmergencyCallButton.setOnClickListener(this); mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 75f466a35fdd..7f6327dd88dd 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -498,6 +498,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { return; } + int width = WRAP_CONTENT; if (st.decorView == null || st.refreshDecorView) { if (st.decorView == null) { // Initialize the panel decor, this will populate st.decorView @@ -523,6 +524,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // If the contents is fill parent for the width, set the // corresponding background backgroundResId = st.fullBackground; + width = MATCH_PARENT; } else { // Otherwise, set the normal panel background backgroundResId = st.background; @@ -546,7 +548,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { st.isHandled = false; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - WRAP_CONTENT, WRAP_CONTENT, + width, WRAP_CONTENT, st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG, WindowManager.LayoutParams.FLAG_DITHER | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM @@ -709,9 +711,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mActionBar.isOverflowReserved()) { if (mActionBar.getVisibility() == View.VISIBLE) { if (!mActionBar.isOverflowMenuShowing()) { - final Callback cb = getCallback(); - if (cb != null && !isDestroyed() && - cb.onPreparePanel(featureId, st.createdPanelView, st.menu)) { + if (!isDestroyed() && preparePanel(st, event)) { playSoundEffect = mActionBar.showOverflowMenu(); } } else { diff --git a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java b/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java index 544bb3df1523..7e8d547dff8b 100644 --- a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java @@ -109,7 +109,7 @@ public class SimPukUnlockScreen extends LinearLayout implements KeyguardScreen, mDelPinButton.setOnClickListener(this); - mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall); + mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton); mOkButton = (TextView) findViewById(R.id.ok); mHeaderText.setText(R.string.keyguard_password_enter_puk_code); diff --git a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java index 7255c274bae5..ec917f07d070 100644 --- a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java +++ b/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java @@ -99,7 +99,7 @@ public class SimUnlockScreen extends LinearLayout implements KeyguardScreen, Vie mOkButton.setOnClickListener(this); - mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall); + mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton); mEmergencyCallButton.setOnClickListener(this); mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton); diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index 44df5b552df0..f716e6375ede 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -2936,7 +2936,7 @@ AudioFlinger::PlaybackThread::Track::Track( mStreamType = streamType; // NOTE: audio_track_cblk_t::frameSize for 8 bit PCM data is based on a sample size of // 16 bit because data is converted to 16 bit before being stored in buffer by AudioTrack - mCblk->frameSize = audio_is_linear_pcm(format) ? mChannelCount * sizeof(int16_t) : sizeof(int8_t); + mCblk->frameSize = audio_is_linear_pcm(format) ? mChannelCount * audio_bytes_per_sample(format) : sizeof(uint8_t); } } diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index 8c07e155847a..5e54d618792d 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -255,10 +255,7 @@ class AlarmManagerService extends IAlarmManager.Stub { // Update the kernel timezone information // Kernel tracks time offsets as 'minutes west of GMT' - int gmtOffset = zone.getRawOffset(); - if (zone.inDaylightTime(new Date(System.currentTimeMillis()))) { - gmtOffset += zone.getDSTSavings(); - } + int gmtOffset = zone.getOffset(System.currentTimeMillis()); setKernelTimezone(mDescriptor, -(gmtOffset / 60000)); } @@ -759,9 +756,8 @@ class AlarmManagerService extends IAlarmManager.Stub { // based off of the current Zone gmt offset + userspace tracked // daylight savings information. TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY)); - int gmtOffset = (zone.getRawOffset() + zone.getDSTSavings()) / 60000; - - setKernelTimezone(mDescriptor, -(gmtOffset)); + int gmtOffset = zone.getOffset(System.currentTimeMillis()); + setKernelTimezone(mDescriptor, -(gmtOffset / 60000)); scheduleDateChangedEvent(); } } diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index aa3dfa6201c0..e6f443a54bcd 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -466,12 +466,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b); mTethering = new Tethering(mContext, nmService, mHandler.getLooper()); - mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) || - !mTethering.isDunRequired()) && - (mTethering.getTetherableUsbRegexs().length != 0 || + mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 || mTethering.getTetherableWifiRegexs().length != 0 || mTethering.getTetherableBluetoothRegexs().length != 0) && - mTethering.getUpstreamIfaceRegexs().length != 0); + mTethering.getUpstreamIfaceTypes().length != 0); mVpn = new Vpn(mContext, new VpnCallback()); @@ -1576,12 +1574,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } addPrivateDnsRoutes(mNetTrackers[netType]); } - - /** Notify TetheringService if interface name has been changed. */ - if (TextUtils.equals(mNetTrackers[netType].getNetworkInfo().getReason(), - Phone.REASON_LINK_PROPERTIES_CHANGED)) { - handleTetherIfaceChange(netType); - } } else { if (mNetConfigs[netType].isDefault()) { removeDefaultRoute(mNetTrackers[netType]); @@ -2412,14 +2404,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private void handleTetherIfaceChange(int type) { - String iface = mNetTrackers[type].getLinkProperties().getInterfaceName(); - - if (isTetheringSupported()) { - mTethering.handleTetherIfaceChange(iface); - } - } - private void log(String s) { Slog.d(TAG, s); } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index fbde9d138ecf..8037d7a41682 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -35,6 +35,8 @@ import org.xmlpull.v1.XmlSerializer; import android.app.ActivityManagerNative; import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ComponentName; import android.content.ContentResolver; @@ -52,6 +54,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; +import android.graphics.BitmapFactory; import android.inputmethodservice.InputMethodService; import android.os.Binder; import android.os.Environment; @@ -154,6 +157,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans = new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE); + // Ongoing notification + private final NotificationManager mNotificationManager; + private final Notification mImeSwitcherNotification; + private final PendingIntent mImeSwitchPendingIntent; + private final boolean mShowOngoingImeSwitcherForPhones; + private boolean mNotificationShown; + class SessionState { final ClientState client; final IInputMethod method; @@ -508,6 +518,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub handleMessage(msg); } }); + + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + mImeSwitcherNotification = new Notification(); + mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default; + mImeSwitcherNotification.when = 0; + mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT; + mImeSwitcherNotification.tickerText = null; + mImeSwitcherNotification.defaults = 0; // please be quiet + mImeSwitcherNotification.sound = null; + mImeSwitcherNotification.vibrate = null; + Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mImeSwitchPendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); + mShowOngoingImeSwitcherForPhones = mRes.getBoolean( + com.android.internal.R.bool.show_ongoing_ime_switcher); + synchronized (mMethodMap) { mFileManager = new InputMethodFileManager(mMethodMap); } @@ -522,6 +551,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mStatusBar = statusBar; statusBar.setIconVisibility("ime", false); + mNotificationShown = false; // mSettings should be created before buildInputMethodListLocked mSettings = new InputMethodSettings( @@ -1022,6 +1052,32 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private boolean needsToShowImeSwitchOngoingNotification() { + if (!mShowOngoingImeSwitcherForPhones) return false; + synchronized (mMethodMap) { + List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); + final int N = imis.size(); + int count = 0; + for(int i = 0; i < N; ++i) { + final InputMethodInfo imi = imis.get(i); + final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked( + imi, true); + final int subtypeCount = subtypes.size(); + if (subtypeCount == 0) { + ++count; + } else { + for (int j = 0; j < subtypeCount; ++j) { + if (!subtypes.get(j).isAuxiliary()) { + ++count; + } + } + } + if (count > 1) return true; + } + } + return false; + } + @Override public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { int uid = Binder.getCallingUid(); @@ -1036,6 +1092,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mImeWindowVis = vis; mBackDisposition = backDisposition; mStatusBar.setImeWindowStatus(token, vis, backDisposition); + final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0; + if (iconVisibility && needsToShowImeSwitchOngoingNotification()) { + final PackageManager pm = mContext.getPackageManager(); + final CharSequence label = mMethodMap.get(mCurMethodId).loadLabel(pm); + final CharSequence title = mRes.getText( + com.android.internal.R.string.select_input_method); + mImeSwitcherNotification.setLatestEventInfo( + mContext, title, label, mImeSwitchPendingIntent); + mNotificationManager.notify( + com.android.internal.R.string.select_input_method, + mImeSwitcherNotification); + mNotificationShown = true; + } else { + if (mNotificationShown) { + mNotificationManager.cancel( + com.android.internal.R.string.select_input_method); + mNotificationShown = false; + } + } } } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index a8cb3337e8c3..2b01c5e158e2 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -19,6 +19,7 @@ package com.android.server; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; +import static android.Manifest.permission.MANAGE_NETWORK_POLICY; import android.content.Context; import android.content.pm.PackageManager; @@ -1022,6 +1023,12 @@ class NetworkManagementService extends INetworkManagementService.Stub { return getInterfaceThrottle(iface, false); } + @Override + public void setBandwidthControlEnabled(boolean enabled) { + mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + mConnector.doCommand(String.format("bandwidth %s", (enabled ? "enable" : "disable"))); + } + /** * Utility method to read a single plain-text {@link Long} from the given * {@link File}, usually from a {@code /proc/} filesystem. diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index a9dfb22310d7..f6dd43a815b6 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -32,6 +32,7 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.ContentObserver; +import android.graphics.Rect; import android.net.Uri; import android.os.Binder; import android.os.Handler; @@ -814,6 +815,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final AtomicInteger mInteractionIdCounter = new AtomicInteger(); + final Rect mTempBounds = new Rect(); + // the events pending events to be dispatched to this service final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<AccessibilityEvent>(); @@ -932,9 +935,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityNodeInfo info = mCallback.getFindAccessibilityNodeInfoResultAndClear( interactionId); if (info != null) { + applyCompatibilityScaleIfNeeded(info); info.setConnection(this); + info.setSealed(true); } - info.setSealed(true); return info; } catch (RemoteException re) { if (DEBUG) { @@ -979,6 +983,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final int infoCount = infos.size(); for (int i = 0; i < infoCount; i++) { AccessibilityNodeInfo info = infos.get(i); + applyCompatibilityScaleIfNeeded(info); info.setConnection(this); info.setSealed(true); } @@ -1019,6 +1024,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityNodeInfo info = mCallback.getFindAccessibilityNodeInfoResultAndClear(interactionId); if (info != null) { + applyCompatibilityScaleIfNeeded(info); info.setConnection(this); info.setSealed(true); } @@ -1093,6 +1099,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } return mWindowIdToInteractionConnectionMap.get(windowId); } + + private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info) { + IBinder windowToken = mWindowIdToWindowTokenMap.get(info.getWindowId()); + final float scale = mWindowManagerService.getWindowCompatibilityScale(windowToken); + + if (scale == 1.0f) { + return; + } + + Rect bounds = mTempBounds; + info.getBoundsInParent(bounds); + bounds.scale(scale); + info.setBoundsInParent(bounds); + + info.getBoundsInScreen(bounds); + bounds.scale(scale); + info.setBoundsInScreen(bounds); + } } final class SecurityPolicy { diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index d8772b82e60e..b94ee587793c 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -73,7 +73,7 @@ import java.util.List; */ final class ActivityStack { static final String TAG = ActivityManagerService.TAG; - static final boolean localLOGV = ActivityManagerService.localLOGV || true; + static final boolean localLOGV = ActivityManagerService.localLOGV; static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH; static final boolean DEBUG_PAUSE = ActivityManagerService.DEBUG_PAUSE; static final boolean DEBUG_VISBILITY = ActivityManagerService.DEBUG_VISBILITY; diff --git a/services/java/com/android/server/am/AppErrorDialog.java b/services/java/com/android/server/am/AppErrorDialog.java index a769c05c1648..917259acf1b5 100644 --- a/services/java/com/android/server/am/AppErrorDialog.java +++ b/services/java/com/android/server/am/AppErrorDialog.java @@ -60,12 +60,12 @@ class AppErrorDialog extends BaseErrorDialog { setCancelable(false); - setButton(DialogInterface.BUTTON_POSITIVE, + setButton(DialogInterface.BUTTON_NEGATIVE, res.getText(com.android.internal.R.string.force_close), mHandler.obtainMessage(FORCE_QUIT)); if (app.errorReportReceiver != null) { - setButton(DialogInterface.BUTTON_NEGATIVE, + setButton(DialogInterface.BUTTON_POSITIVE, res.getText(com.android.internal.R.string.report), mHandler.obtainMessage(FORCE_QUIT_AND_REPORT)); } diff --git a/services/java/com/android/server/am/AppNotRespondingDialog.java b/services/java/com/android/server/am/AppNotRespondingDialog.java index b2737dcd23aa..b40f87a90edb 100644 --- a/services/java/com/android/server/am/AppNotRespondingDialog.java +++ b/services/java/com/android/server/am/AppNotRespondingDialog.java @@ -77,7 +77,7 @@ class AppNotRespondingDialog extends BaseErrorDialog { ? res.getString(resid, name1.toString(), name2.toString()) : res.getString(resid, name1.toString())); - setButton(DialogInterface.BUTTON_POSITIVE, + setButton(DialogInterface.BUTTON_NEGATIVE, res.getText(com.android.internal.R.string.force_close), mHandler.obtainMessage(FORCE_CLOSE)); setButton(DialogInterface.BUTTON_NEUTRAL, @@ -85,7 +85,7 @@ class AppNotRespondingDialog extends BaseErrorDialog { mHandler.obtainMessage(WAIT)); if (app.errorReportReceiver != null) { - setButton(DialogInterface.BUTTON_NEGATIVE, + setButton(DialogInterface.BUTTON_POSITIVE, res.getText(com.android.internal.R.string.report), mHandler.obtainMessage(WAIT_AND_REPORT)); } diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 15e67d0e2dc4..911cac2bcc3d 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -57,7 +57,9 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.InetAddress; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.Set; /** @@ -82,7 +84,15 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private String[] mTetherableUsbRegexs; private String[] mTetherableWifiRegexs; private String[] mTetherableBluetoothRegexs; - private String[] mUpstreamIfaceRegexs; + private Collection<Integer> mUpstreamIfaceTypes; + + private static final Integer MOBILE_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE); + private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI); + private static final Integer DUN_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_DUN); + + // if we have to connect to mobile, what APN type should we use? Calculated by examining the + // upstream type list and the DUN_REQUIRED secure-setting + private int mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_NONE; private INetworkManagementService mNMService; private Looper mLooper; @@ -112,9 +122,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8"; private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4"; - // resampled each time we turn on tethering - used as cache for settings/config-val - private boolean mDunRequired; // configuration info - must use DUN apn on 3g - private StateMachine mTetherMasterSM; private Notification mTetheredNotification; @@ -159,7 +166,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) { mDhcpRange = DHCP_DEFAULT_RANGE; } - mDunRequired = false; // resample when we turn on mTetherableUsbRegexs = context.getResources().getStringArray( com.android.internal.R.array.config_tether_usb_regexs); @@ -167,8 +173,15 @@ public class Tethering extends INetworkManagementEventObserver.Stub { com.android.internal.R.array.config_tether_wifi_regexs); mTetherableBluetoothRegexs = context.getResources().getStringArray( com.android.internal.R.array.config_tether_bluetooth_regexs); - mUpstreamIfaceRegexs = context.getResources().getStringArray( - com.android.internal.R.array.config_tether_upstream_regexs); + int ifaceTypes[] = context.getResources().getIntArray( + com.android.internal.R.array.config_tether_upstream_types); + mUpstreamIfaceTypes = new ArrayList(); + for (int i : ifaceTypes) { + mUpstreamIfaceTypes.add(new Integer(i)); + } + + // check if the upstream type list needs to be modified due to secure-settings + checkDunRequired(); // TODO - remove and rely on real notifications of the current iface mDnsServers = new String[2]; @@ -509,9 +522,9 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } try { if (enabled) { - usbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_RNDIS); + usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false); } else { - usbManager.setPrimaryFunction(null); + usbManager.setCurrentFunction(null, false); } } catch (Exception e) { Log.e(TAG, "Error toggling usb RNDIS", e); @@ -582,16 +595,44 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return mTetherableBluetoothRegexs; } - public String[] getUpstreamIfaceRegexs() { - return mUpstreamIfaceRegexs; + public int[] getUpstreamIfaceTypes() { + int values[] = new int[mUpstreamIfaceTypes.size()]; + Iterator<Integer> iterator = mUpstreamIfaceTypes.iterator(); + for (int i=0; i < mUpstreamIfaceTypes.size(); i++) { + values[i] = iterator.next(); + } + return values; } - public boolean isDunRequired() { - boolean defaultVal = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_tether_dun_required); - boolean result = (Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.TETHER_DUN_REQUIRED, (defaultVal ? 1 : 0)) == 1); - return result; + public void checkDunRequired() { + int requiredApn = ((Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.TETHER_DUN_REQUIRED, 0) == 1) ? + ConnectivityManager.TYPE_MOBILE_DUN : + ConnectivityManager.TYPE_MOBILE_HIPRI); + if (mPreferredUpstreamMobileApn != requiredApn) { + if (requiredApn == ConnectivityManager.TYPE_MOBILE_DUN) { + while (mUpstreamIfaceTypes.contains(MOBILE_TYPE)) { + mUpstreamIfaceTypes.remove(MOBILE_TYPE); + } + while (mUpstreamIfaceTypes.contains(HIPRI_TYPE)) { + mUpstreamIfaceTypes.remove(HIPRI_TYPE); + } + if (mUpstreamIfaceTypes.contains(DUN_TYPE) == false) { + mUpstreamIfaceTypes.add(DUN_TYPE); + } + } else { + while (mUpstreamIfaceTypes.contains(DUN_TYPE)) { + mUpstreamIfaceTypes.remove(DUN_TYPE); + } + if (mUpstreamIfaceTypes.contains(MOBILE_TYPE) == false) { + mUpstreamIfaceTypes.add(MOBILE_TYPE); + } + if (mUpstreamIfaceTypes.contains(HIPRI_TYPE) == false) { + mUpstreamIfaceTypes.add(HIPRI_TYPE); + } + } + mPreferredUpstreamMobileApn = requiredApn; + } } public String[] getTetheredIfaces() { @@ -648,17 +689,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return retVal; } - public void handleTetherIfaceChange(String iface) { - // check if iface is white listed - for (String regex : mUpstreamIfaceRegexs) { - if (iface.matches(regex)) { - if (DEBUG) Log.d(TAG, "Tethering got Interface Change"); - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_IFACE_CHANGED, iface); - break; - } - } - } - class TetherInterfaceSM extends StateMachine { // notification from the master SM that it's not in tether mode static final int CMD_TETHER_MODE_DEAD = 1; @@ -1051,8 +1081,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { static final int CMD_CELL_CONNECTION_RENEW = 4; // we don't have a valid upstream conn, check again after a delay static final int CMD_RETRY_UPSTREAM = 5; - // received an indication that upstream interface has changed - static final int CMD_IFACE_CHANGED = 6; // This indicates what a timeout event relates to. A state that // sends itself a delayed timeout event and handles incoming timeout events @@ -1072,7 +1100,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private ArrayList mNotifyList; private int mCurrentConnectionSequence; - private boolean mMobileReserved = false; + private int mMobileApnReserved = ConnectivityManager.TYPE_NONE; private String mUpstreamIfaceName = null; @@ -1111,22 +1139,34 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public boolean processMessage(Message m) { return false; } - protected boolean turnOnMobileConnection() { + protected String enableString(int apnType) { + switch (apnType) { + case ConnectivityManager.TYPE_MOBILE_DUN: + return Phone.FEATURE_ENABLE_DUN_ALWAYS; + case ConnectivityManager.TYPE_MOBILE: + case ConnectivityManager.TYPE_MOBILE_HIPRI: + return Phone.FEATURE_ENABLE_HIPRI; + } + return null; + } + protected boolean turnOnUpstreamMobileConnection(int apnType) { boolean retValue = true; - if (mMobileReserved) return retValue; + if (apnType == ConnectivityManager.TYPE_NONE) return false; + if (apnType != mMobileApnReserved) turnOffUpstreamMobileConnection(); IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); int result = Phone.APN_REQUEST_FAILED; + String enableString = enableString(apnType); + if (enableString == null) return false; try { result = cm.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, - (mDunRequired ? Phone.FEATURE_ENABLE_DUN_ALWAYS : - Phone.FEATURE_ENABLE_HIPRI), new Binder()); + enableString, new Binder()); } catch (Exception e) { } switch (result) { case Phone.APN_ALREADY_ACTIVE: case Phone.APN_REQUEST_STARTED: - mMobileReserved = true; + mMobileApnReserved = apnType; Message m = obtainMessage(CMD_CELL_CONNECTION_RENEW); m.arg1 = ++mCurrentConnectionSequence; sendMessageDelayed(m, CELL_CONNECTION_RENEW_MS); @@ -1139,18 +1179,17 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return retValue; } - protected boolean turnOffMobileConnection() { - if (mMobileReserved) { + protected boolean turnOffUpstreamMobileConnection() { + if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); try { cm.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, - (mDunRequired? Phone.FEATURE_ENABLE_DUN_ALWAYS : - Phone.FEATURE_ENABLE_HIPRI)); + enableString(mMobileApnReserved)); } catch (Exception e) { return false; } - mMobileReserved = false; + mMobileApnReserved = ConnectivityManager.TYPE_NONE; } return true; } @@ -1196,108 +1235,55 @@ public class Tethering extends INetworkManagementEventObserver.Stub { transitionTo(mInitialState); return true; } - protected String findActiveUpstreamIface() { - // check for what iface we can use - if none found switch to error. + + protected void chooseUpstreamType(boolean tryCell) { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); + int upType = ConnectivityManager.TYPE_NONE; + String iface = null; - try { - LinkProperties defaultProp = cm.getActiveLinkProperties(); - if (defaultProp != null) { - String iface = defaultProp.getInterfaceName(); - for(String regex : mUpstreamIfaceRegexs) { - if (iface.matches(regex)) return iface; - } - } - } catch (RemoteException e) { } - - String[] ifaces = new String[0]; - try { - ifaces = mNMService.listInterfaces(); - } catch (Exception e) { - Log.e(TAG, "Error listing Interfaces", e); - return null; - } - - for (String regex : mUpstreamIfaceRegexs) { - for (String iface : ifaces) { - if (iface.matches(regex)) { - // verify it is active - InterfaceConfiguration ifcg = null; - try { - ifcg = mNMService.getInterfaceConfig(iface); - if (ifcg.isActive()) { - return iface; - } - } catch (Exception e) { - Log.e(TAG, "Error getting iface config", e); - // ignore - try next - continue; - } - } + for (Integer netType : mUpstreamIfaceTypes) { + NetworkInfo info = null; + try { + info = cm.getNetworkInfo(netType.intValue()); + } catch (RemoteException e) { } + if ((info != null) && info.isConnected()) { + upType = netType.intValue(); + break; } } - return null; - } - protected void chooseUpstreamType(boolean tryCell) { - // decide if the current upstream is good or not and if not - // do something about it (start up DUN if required or HiPri if not) - String iface = findActiveUpstreamIface(); - IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b); - mMobileReserved = false; if (DEBUG) { - Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired =" - + mDunRequired + ", iface=" + iface); + Log.d(TAG, "chooseUpstreamType(" + tryCell + "), preferredApn =" + + mPreferredUpstreamMobileApn + ", got type=" + upType); } - if (iface != null) { - try { - if (mDunRequired) { - // check if Dun is on - we can use that - NetworkInfo info = cm.getNetworkInfo( - ConnectivityManager.TYPE_MOBILE_DUN); - if (info.isConnected()) { - if (DEBUG) Log.d(TAG, "setting dun ifacename =" + iface); - // even if we're already connected - it may be somebody else's - // refcount, so add our own - turnOnMobileConnection(); - } else { - // verify the iface is not the default mobile - can't use that! - info = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); - if (info.isConnected()) { - iface = null; // can't accept this one - } - } - } else { - if (DEBUG) Log.d(TAG, "checking if hipri brought us this connection"); - NetworkInfo info = cm.getNetworkInfo( - ConnectivityManager.TYPE_MOBILE_HIPRI); - if (info.isConnected()) { - if (DEBUG) Log.d(TAG, "yes - hipri in use"); - // even if we're already connected - it may be sombody else's - // refcount, so add our own - turnOnMobileConnection(); - } - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException calling ConnectivityManager", e); - iface = null; - } + + // if we're on DUN, put our own grab on it + if (upType == ConnectivityManager.TYPE_MOBILE_DUN || + upType == ConnectivityManager.TYPE_MOBILE_HIPRI) { + turnOnUpstreamMobileConnection(upType); } - // may have been set to null in the if above - if (iface == null ) { - boolean success = false; - if (tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) { - success = turnOnMobileConnection(); + + if (upType == ConnectivityManager.TYPE_NONE) { + boolean tryAgainLater = true; + if ((tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) && + (turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn) == true)) { + // we think mobile should be coming up - don't set a retry + tryAgainLater = false; } - if (!success) { - // wait for things to settle and retry + if (tryAgainLater) { sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } + } else { + LinkProperties linkProperties = null; + try { + linkProperties = cm.getLinkProperties(upType); + } catch (RemoteException e) { } + if (linkProperties != null) iface = linkProperties.getInterfaceName(); } notifyTetheredOfNewUpstreamIface(iface); } + protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { if (DEBUG) Log.d(TAG, "notifying tethered with iface =" + ifaceName); mUpstreamIfaceName = ifaceName; @@ -1312,7 +1298,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { class InitialState extends TetherMasterUtilState { @Override public void enter() { - mMobileReserved = false; } @Override public boolean processMessage(Message message) { @@ -1320,7 +1305,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: - mDunRequired = isDunRequired(); + checkDunRequired(); TetherInterfaceSM who = (TetherInterfaceSM)message.obj; if (DEBUG) Log.d(TAG, "Tether Mode requested by " + who.toString()); mNotifyList.add(who); @@ -1354,7 +1339,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } @Override public void exit() { - turnOffMobileConnection(); + turnOffUpstreamMobileConnection(); notifyTetheredOfNewUpstreamIface(null); } @Override @@ -1392,19 +1377,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { Log.d(TAG, "renewing mobile connection - requeuing for another " + CELL_CONNECTION_RENEW_MS + "ms"); } - mMobileReserved = false; // need to renew it - turnOnMobileConnection(); + turnOnUpstreamMobileConnection(mMobileApnReserved); } break; case CMD_RETRY_UPSTREAM: chooseUpstreamType(mTryCell); mTryCell = !mTryCell; break; - case CMD_IFACE_CHANGED: - String iface = (String)message.obj; - if (DEBUG) Log.d(TAG, "Activie upstream interface changed: " + iface); - notifyTetheredOfNewUpstreamIface(iface); - break; default: retValue = false; break; diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 043a581c880f..a80bc043a4fe 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -27,6 +27,7 @@ import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_REMOVED; +import static android.provider.Settings.Secure.NETSTATS_ENABLED; import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION; import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY; import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD; @@ -134,6 +135,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * Settings that can be changed externally. */ public interface NetworkStatsSettings { + public boolean getEnabled(); public long getPollInterval(); public long getPersistThreshold(); public long getNetworkBucketDuration(); @@ -206,6 +208,18 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } public void systemReady() { + if (mSettings.getEnabled()) { + try { + // enable low-level bandwidth stats and control + // TODO: consider shipping with this enabled by default + mNetworkManager.setBandwidthControlEnabled(true); + } catch (RemoteException e) { + Slog.e(TAG, "problem enabling bandwidth controls", e); + } + } else { + Slog.w(TAG, "detailed network stats disabled"); + } + synchronized (mStatsLock) { // read historical network stats from disk, since policy service // might need them right away. we delay loading detailed UID stats @@ -1040,6 +1054,9 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return Settings.Secure.getLong(mResolver, name, def); } + public boolean getEnabled() { + return Settings.Secure.getInt(mResolver, NETSTATS_ENABLED, 1) != 0; + } public long getPollInterval() { return getSecureLong(NETSTATS_POLL_INTERVAL, 15 * MINUTE_IN_MILLIS); } diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index b7f9d5ccb04a..918f1b6b8355 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -77,9 +77,8 @@ public class UsbDeviceManager { private static final int MSG_UPDATE_STATE = 0; private static final int MSG_ENABLE_ADB = 1; - private static final int MSG_SET_PRIMARY_FUNCTION = 2; - private static final int MSG_SET_DEFAULT_FUNCTION = 3; - private static final int MSG_SYSTEM_READY = 4; + private static final int MSG_SET_CURRENT_FUNCTION = 2; + private static final int MSG_SYSTEM_READY = 3; // Delay for debouncing USB disconnects. // We often get rapid connect/disconnect events when enabling USB functions, @@ -227,7 +226,7 @@ public class UsbDeviceManager { mHandler.updateState(state); } else if ("START".equals(accessory)) { Slog.d(TAG, "got accessory start"); - setPrimaryFunction(UsbManager.USB_FUNCTION_ACCESSORY); + setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false); } } }; @@ -371,6 +370,14 @@ public class UsbDeviceManager { sendMessage(m); } + public void sendMessage(int what, Object arg0, boolean arg1) { + removeMessages(what); + Message m = Message.obtain(this, what); + m.obj = arg0; + m.arg1 = (arg1 ? 1 : 0); + sendMessage(m); + } + public void updateState(String state) { int connected, configured; @@ -395,24 +402,30 @@ public class UsbDeviceManager { sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0); } - private boolean setUsbConfig(String config) { - // set the new configuration - SystemProperties.set("sys.usb.config", config); + private boolean waitForState(String state) { // wait for the transition to complete. // give up after 1 second. for (int i = 0; i < 20; i++) { // State transition is done when sys.usb.conf.done is set to the new configuration - if (config.equals(SystemProperties.get("sys.usb.state"))) return true; + if (state.equals(SystemProperties.get("sys.usb.state"))) return true; try { // try again in 50ms Thread.sleep(50); } catch (InterruptedException e) { } } + Log.e(TAG, "waitForState(" + state + ") FAILED"); return false; } - private void setCurrentFunctions(String functions) { + private boolean setUsbConfig(String config) { + Log.d(TAG, "setUsbConfig(" + config + ")"); + // set the new configuration + SystemProperties.set("sys.usb.config", config); + return waitForState(config); + } + + private void doSetCurrentFunctions(String functions) { if (!mCurrentFunctions.equals(functions)) { if (!setUsbConfig("none") || !setUsbConfig(functions)) { Log.e(TAG, "Failed to switch USB configuration to " + functions); @@ -428,17 +441,14 @@ public class UsbDeviceManager { if (enable != mAdbEnabled) { mAdbEnabled = enable; String functions; + // Due to the persist.sys.usb.config property trigger, changing adb state requires + // switching to default function if (enable) { - functions = addFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB); - mDefaultFunctions = addFunction(mDefaultFunctions, - UsbManager.USB_FUNCTION_ADB); + functions = addFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB); } else { - functions = removeFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB); - mDefaultFunctions = removeFunction(mDefaultFunctions, - UsbManager.USB_FUNCTION_ADB); + functions = removeFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB); } - SystemProperties.set("persist.sys.usb.config", mDefaultFunctions); - setCurrentFunctions(functions); + setCurrentFunction(functions, true); updateAdbNotification(mAdbEnabled && mConnected); } } @@ -449,7 +459,7 @@ public class UsbDeviceManager { } else { functionList = removeFunction(functionList, UsbManager.USB_FUNCTION_ADB); } - setCurrentFunctions(functionList); + doSetCurrentFunctions(functionList); } private void updateCurrentAccessory() { @@ -503,8 +513,6 @@ public class UsbDeviceManager { @Override public void handleMessage(Message msg) { - String function; - switch (msg.what) { case MSG_UPDATE_STATE: mConnected = (msg.arg1 == 1); @@ -518,7 +526,7 @@ public class UsbDeviceManager { if (!mConnected) { // restore defaults when USB is disconnected - setCurrentFunctions(mDefaultFunctions); + doSetCurrentFunctions(mDefaultFunctions); } if (mSystemReady) { updateUsbState(); @@ -527,20 +535,31 @@ public class UsbDeviceManager { case MSG_ENABLE_ADB: setAdbEnabled(msg.arg1 == 1); break; - case MSG_SET_PRIMARY_FUNCTION: - function = (String)msg.obj; - if (function == null) { - function = mDefaultFunctions; - } - setEnabledFunctions(function); - break; - case MSG_SET_DEFAULT_FUNCTION: - function = (String)msg.obj; - if (mAdbEnabled) { - function = addFunction(function, UsbManager.USB_FUNCTION_ADB); + case MSG_SET_CURRENT_FUNCTION: + String function = (String)msg.obj; + boolean makeDefault = (msg.arg1 == 1); + if (makeDefault) { + if (function == null) { + throw new NullPointerException(); + } + if (mAdbEnabled) { + function = addFunction(function, UsbManager.USB_FUNCTION_ADB); + } + + setUsbConfig("none"); + // setting this property will change the current USB state + // via a property trigger + SystemProperties.set("persist.sys.usb.config", function); + if (waitForState(function)) { + mCurrentFunctions = function; + mDefaultFunctions = function; + } + } else { + if (function == null) { + function = mDefaultFunctions; + } + setEnabledFunctions(function); } - SystemProperties.set("persist.sys.usb.config", function); - mDefaultFunctions = function; break; case MSG_SYSTEM_READY: updateUsbNotification(mConnected); @@ -588,15 +607,8 @@ public class UsbDeviceManager { return nativeOpenAccessory(); } - public void setPrimaryFunction(String function) { - mHandler.sendMessage(MSG_SET_PRIMARY_FUNCTION, function); - } - - public void setDefaultFunction(String function) { - if (function == null) { - throw new NullPointerException(); - } - mHandler.sendMessage(MSG_SET_DEFAULT_FUNCTION, function); + public void setCurrentFunction(String function, boolean makeDefault) { + mHandler.sendMessage(MSG_SET_CURRENT_FUNCTION, function, makeDefault); } public void setMassStorageBackingFile(String path) { diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java index 193638f64186..9f2c17aa8ae5 100644 --- a/services/java/com/android/server/usb/UsbService.java +++ b/services/java/com/android/server/usb/UsbService.java @@ -146,19 +146,10 @@ public class UsbService extends IUsbManager.Stub { mSettingsManager.clearDefaults(packageName); } - public void setPrimaryFunction(String function) { + public void setCurrentFunction(String function, boolean makeDefault) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); if (mDeviceManager != null) { - mDeviceManager.setPrimaryFunction(function); - } else { - throw new IllegalStateException("USB device mode not supported"); - } - } - - public void setDefaultFunction(String function) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - if (mDeviceManager != null) { - mDeviceManager.setDefaultFunction(function); + mDeviceManager.setCurrentFunction(function, makeDefault); } else { throw new IllegalStateException("USB device mode not supported"); } diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java index 3be8af63a1d8..68066344586a 100644 --- a/services/java/com/android/server/wm/InputMonitor.java +++ b/services/java/com/android/server/wm/InputMonitor.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.graphics.Rect; +import android.os.Binder; import android.os.Process; import android.os.RemoteException; import android.util.Log; @@ -152,6 +153,8 @@ final class InputMonitor { } mUpdateInputWindowsNeeded = false; + if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED updateInputWindowsLw"); + // Populate the input window list with information about all of the windows that // could potentially receive input. // As an optimization, we could try to prune the list of windows but this turns @@ -232,6 +235,8 @@ final class InputMonitor { // Clear the list in preparation for the next round. // Also avoids keeping InputChannel objects referenced unnecessarily. mTempInputWindows.clear(); + + if (false) Slog.d(WindowManagerService.TAG, "<<<<<<< EXITED updateInputWindowsLw"); } /* Notifies that the input device configuration has changed. */ diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java index 0f09356f95e5..50b251f22ce0 100644 --- a/services/java/com/android/server/wm/Session.java +++ b/services/java/com/android/server/wm/Session.java @@ -153,11 +153,13 @@ final class Session extends IWindowSession.Stub int requestedWidth, int requestedHeight, int viewFlags, boolean insetsPending, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { - //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); + if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED relayout from " + + Binder.getCallingPid()); int res = mService.relayoutWindow(this, window, attrs, requestedWidth, requestedHeight, viewFlags, insetsPending, outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface); - //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); + if (false) Slog.d(WindowManagerService.TAG, "<<<<<< EXITING relayout to " + + Binder.getCallingPid()); return res; } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 1c87f5b4002d..3bf309b896f8 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -2776,6 +2776,13 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } + public float getWindowCompatibilityScale(IBinder windowToken) { + synchronized (mWindowMap) { + WindowState windowState = mWindowMap.get(windowToken); + return (windowState != null) ? windowState.mGlobalScale : 1.0f; + } + } + private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) { if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg=" + (lp != null ? lp.packageName : null) @@ -5557,10 +5564,10 @@ public class WindowManagerService extends IWindowManager.Stub private int reduceCompatConfigWidthSize(int curSize, int rotation, DisplayMetrics dm, int dw, int dh) { - dm.unscaledWidthPixels = mPolicy.getNonDecorDisplayWidth(rotation, dw); - dm.unscaledHeightPixels = mPolicy.getNonDecorDisplayHeight(rotation, dh); + dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(rotation, dw); + dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(rotation, dh); float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); - int size = (int)(((dm.unscaledWidthPixels / scale) / dm.density) + .5f); + int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f); if (curSize == 0 || size < curSize) { curSize = size; } @@ -5635,9 +5642,9 @@ public class WindowManagerService extends IWindowManager.Stub // Override display width and height with what we are computing, // to be sure they remain consistent. - dm.widthPixels = dm.unscaledWidthPixels = mAppDisplayWidth + dm.widthPixels = dm.noncompatWidthPixels = mAppDisplayWidth = mPolicy.getNonDecorDisplayWidth(mRotation, dw); - dm.heightPixels = dm.unscaledHeightPixels = mAppDisplayHeight + dm.heightPixels = dm.noncompatHeightPixels = mAppDisplayHeight = mPolicy.getNonDecorDisplayHeight(mRotation, dh); mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm, @@ -7817,18 +7824,30 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Placing surface #" + i + " " + w.mSurface + ": new=" + w.mShownFrame); - int width, height; - if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) { - // for a scaled surface, we just want to use - // the requested size. - width = w.mRequestedWidth; - height = w.mRequestedHeight; - } else { - width = w.mCompatFrame.width(); - height = w.mCompatFrame.height(); - } - if (w.mSurface != null) { + int width, height; + if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) { + // for a scaled surface, we just want to use + // the requested size. + width = w.mRequestedWidth; + height = w.mRequestedHeight; + } else { + width = w.mCompatFrame.width(); + height = w.mCompatFrame.height(); + } + + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + final boolean surfaceResized = w.mSurfaceW != width || w.mSurfaceH != height; + if (surfaceResized) { + w.mSurfaceW = width; + w.mSurfaceH = height; + } + if (w.mSurfaceX != w.mShownFrame.left || w.mSurfaceY != w.mShownFrame.top) { try { @@ -7848,21 +7867,11 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (width < 1) { - width = 1; - } - if (height < 1) { - height = 1; - } - - if (w.mSurfaceW != width || w.mSurfaceH != height) { + if (surfaceResized) { try { if (SHOW_TRANSACTIONS) logSurface(w, - "SIZE " + w.mShownFrame.width() + "x" - + w.mShownFrame.height(), null); + "SIZE " + width + "x" + height, null); w.mSurfaceResized = true; - w.mSurfaceW = width; - w.mSurfaceH = height; w.mSurface.setSize(width, height); } catch (RuntimeException e) { // If something goes wrong with the surface (such @@ -7878,9 +7887,9 @@ public class WindowManagerService extends IWindowManager.Stub } if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { - w.mContentInsetsChanged = + w.mContentInsetsChanged |= !w.mLastContentInsets.equals(w.mContentInsets); - w.mVisibleInsetsChanged = + w.mVisibleInsetsChanged |= !w.mLastVisibleInsets.equals(w.mVisibleInsets); boolean configChanged = w.mConfiguration != mCurConfiguration @@ -7892,24 +7901,20 @@ public class WindowManagerService extends IWindowManager.Stub } if (localLOGV) Slog.v(TAG, "Resizing " + w + ": configChanged=" + configChanged - + " last=" + w.mLastCompatFrame + " frame=" + w.mCompatFrame); - boolean frameChanged = !w.mLastCompatFrame.equals(w.mCompatFrame); - if (frameChanged - || w.mContentInsetsChanged + + " last=" + w.mLastFrame + " frame=" + w.mFrame); + w.mLastFrame.set(w.mFrame); + if (w.mContentInsetsChanged || w.mVisibleInsetsChanged || w.mSurfaceResized || configChanged) { if (DEBUG_RESIZE || DEBUG_ORIENTATION) { Slog.v(TAG, "Resize reasons: " - + "frameChanged=" + frameChanged + " contentInsetsChanged=" + w.mContentInsetsChanged + " visibleInsetsChanged=" + w.mVisibleInsetsChanged + " surfaceResized=" + w.mSurfaceResized + " configChanged=" + configChanged); } - w.mLastFrame.set(w.mFrame); - w.mLastCompatFrame.set(w.mCompatFrame); w.mLastContentInsets.set(w.mContentInsets); w.mLastVisibleInsets.set(w.mVisibleInsets); // If the screen is currently frozen, then keep @@ -7944,9 +7949,12 @@ public class WindowManagerService extends IWindowManager.Stub w.mAppToken.allDrawn = false; } } - if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, - "Resizing window " + w + " to " + w.mCompatFrame); - mResizingWindows.add(w); + if (!mResizingWindows.contains(w)) { + if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, + "Resizing window " + w + " to " + w.mSurfaceW + + "x" + w.mSurfaceH); + mResizingWindows.add(w); + } } else if (w.mOrientationChanging) { if (!w.mDrawPending && !w.mCommitDrawPending) { if (DEBUG_ORIENTATION) Slog.v(TAG, @@ -8241,13 +8249,12 @@ public class WindowManagerService extends IWindowManager.Stub if ((DEBUG_RESIZE || DEBUG_ORIENTATION || DEBUG_CONFIGURATION) && configChanged) { Slog.i(TAG, "Sending new config to window " + win + ": " - + win.mCompatFrame.width() + "x" + win.mCompatFrame.height() + + win.mSurfaceW + "x" + win.mSurfaceH + " / " + mCurConfiguration + " / 0x" + Integer.toHexString(diff)); } win.mConfiguration = mCurConfiguration; - win.mClient.resized(win.mCompatFrame.width(), - win.mCompatFrame.height(), win.mLastContentInsets, + win.mClient.resized(win.mSurfaceW, win.mSurfaceH, win.mLastContentInsets, win.mLastVisibleInsets, win.mDrawPending, configChanged ? win.mConfiguration : null); win.mContentInsetsChanged = false; diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 587685e624ff..b370ec9e12cd 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -111,7 +111,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { * applied). */ final Rect mShownFrame = new Rect(); - final Rect mLastShownFrame = new Rect(); /** * Set when we have changed the size of the surface, to know that @@ -182,7 +181,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { // Frame that is scaled to the application's coordinate space when in // screen size compatibility mode. final Rect mCompatFrame = new Rect(); - final Rect mLastCompatFrame = new Rect(); final Rect mContainingFrame = new Rect(); final Rect mDisplayFrame = new Rect(); @@ -1584,15 +1582,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { } pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration); pw.print(prefix); pw.print("mShownFrame="); - mShownFrame.printShortString(pw); - pw.print(" last="); mLastShownFrame.printShortString(pw); - pw.println(); + mShownFrame.printShortString(pw); pw.println(); pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw); pw.print(" last="); mLastFrame.printShortString(pw); pw.println(); if (mEnforceSizeCompat) { pw.print(prefix); pw.print("mCompatFrame="); mCompatFrame.printShortString(pw); - pw.print(" last="); mLastCompatFrame.printShortString(pw); pw.println(); } pw.print(prefix); pw.print("mContainingFrame="); diff --git a/telephony/java/com/android/internal/telephony/ApnContext.java b/telephony/java/com/android/internal/telephony/ApnContext.java index 496c43c5cd55..5ec00e834e9e 100644 --- a/telephony/java/com/android/internal/telephony/ApnContext.java +++ b/telephony/java/com/android/internal/telephony/ApnContext.java @@ -89,6 +89,11 @@ public class ApnContext { } public synchronized void setDataConnectionAc(DataConnectionAc dcac) { + if (dcac != null) { + dcac.addApnContext(this); + } else { + if (mDataConnectionAc != null) mDataConnectionAc.removeApnContext(this); + } mDataConnectionAc = dcac; } diff --git a/telephony/java/com/android/internal/telephony/DataCallState.java b/telephony/java/com/android/internal/telephony/DataCallState.java index f5651e0f87e9..fba3184a2e02 100644 --- a/telephony/java/com/android/internal/telephony/DataCallState.java +++ b/telephony/java/com/android/internal/telephony/DataCallState.java @@ -47,6 +47,7 @@ public class DataCallState { public String [] addresses = new String[0]; public String [] dnses = new String[0]; public String[] gateways = new String[0]; + public int suggestedRetryTime = -1; /** * Class returned by onSetupConnectionCompleted. @@ -77,6 +78,7 @@ public class DataCallState { sb.append("DataCallState: {") .append("version=").append(version) .append(" status=").append(status) + .append(" retry=").append(suggestedRetryTime) .append(" cid=").append(cid) .append(" active=").append(active) .append(" type=").append(type) diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java index 5c030fdb4053..cb8b0e562988 100644 --- a/telephony/java/com/android/internal/telephony/DataConnection.java +++ b/telephony/java/com/android/internal/telephony/DataConnection.java @@ -182,6 +182,18 @@ public abstract class DataConnection extends StateMachine { } } + public static class CallSetupException extends Exception { + private int mRetryOverride = -1; + + CallSetupException (int retryOverride) { + mRetryOverride = retryOverride; + } + + public int getRetryOverride() { + return mRetryOverride; + } + } + // ***** Event codes for driving the state machine protected static final int BASE = Protocol.BASE_DATA_CONNECTION; protected static final int EVENT_CONNECT = BASE + 0; @@ -205,6 +217,7 @@ public abstract class DataConnection extends StateMachine { protected long createTime; protected long lastFailTime; protected FailCause lastFailCause; + protected int mRetryOverride = -1; protected static final String NULL_IP = "0.0.0.0"; private int mRefCount; Object userData; @@ -288,7 +301,8 @@ public abstract class DataConnection extends StateMachine { } else { lastFailCause = cause; lastFailTime = timeStamp; - AsyncResult.forMessage(connectionCompletedMsg, cause, new Exception()); + AsyncResult.forMessage(connectionCompletedMsg, cause, + new CallSetupException(mRetryOverride)); } if (DBG) log("notifyConnectionCompleted at " + timeStamp + " cause=" + cause); @@ -430,6 +444,7 @@ public abstract class DataConnection extends StateMachine { createTime = -1; lastFailTime = -1; lastFailCause = FailCause.NONE; + mRetryOverride = -1; mRefCount = 0; mLinkProperties = new LinkProperties(); @@ -482,6 +497,15 @@ public abstract class DataConnection extends StateMachine { return result; } + private int getSuggestedRetryTime(AsyncResult ar) { + int retry = -1; + if (ar.exception == null) { + DataCallState response = (DataCallState) ar.result; + retry = response.suggestedRetryTime; + } + return retry; + } + private DataCallState.SetupResult setLinkProperties(DataCallState response, LinkProperties lp) { // Check if system property dns usable @@ -685,10 +709,12 @@ public abstract class DataConnection extends StateMachine { private FailCause mFailCause = null; private DisconnectParams mDisconnectParams = null; - public void setEnterNotificationParams(ConnectionParams cp, FailCause cause) { + public void setEnterNotificationParams(ConnectionParams cp, FailCause cause, + int retryOverride) { if (VDBG) log("DcInactiveState: setEnterNoticationParams cp,cause"); mConnectionParams = cp; mFailCause = cause; + mRetryOverride = retryOverride; } public void setEnterNotificationParams(DisconnectParams dp) { @@ -808,7 +834,7 @@ public abstract class DataConnection extends StateMachine { // Vendor ril rejected the command and didn't connect. // Transition to inactive but send notifications after // we've entered the mInactive state. - mInactiveState.setEnterNotificationParams(cp, result.mFailCause); + mInactiveState.setEnterNotificationParams(cp, result.mFailCause, -1); transitionTo(mInactiveState); break; case ERR_UnacceptableParameter: @@ -823,7 +849,8 @@ public abstract class DataConnection extends StateMachine { break; case ERR_RilError: // Request failed and mFailCause has the reason - mInactiveState.setEnterNotificationParams(cp, result.mFailCause); + mInactiveState.setEnterNotificationParams(cp, result.mFailCause, + getSuggestedRetryTime(ar)); transitionTo(mInactiveState); break; case ERR_Stale: @@ -848,8 +875,8 @@ public abstract class DataConnection extends StateMachine { } // Transition to inactive but send notifications after // we've entered the mInactive state. - mInactiveState.setEnterNotificationParams(cp, cause); - transitionTo(mInactiveState); + mInactiveState.setEnterNotificationParams(cp, cause, -1); + transitionTo(mInactiveState); } else { if (DBG) { log("DcActivatingState EVENT_GET_LAST_FAIL_DONE is stale cp.tag=" @@ -1016,7 +1043,7 @@ public abstract class DataConnection extends StateMachine { // Transition to inactive but send notifications after // we've entered the mInactive state. mInactiveState.setEnterNotificationParams(cp, - FailCause.UNACCEPTABLE_NETWORK_PARAMETER); + FailCause.UNACCEPTABLE_NETWORK_PARAMETER, -1); transitionTo(mInactiveState); } else { if (DBG) { diff --git a/telephony/java/com/android/internal/telephony/DataConnectionAc.java b/telephony/java/com/android/internal/telephony/DataConnectionAc.java index 62b90aef26cb..e23f1ccc3b8e 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionAc.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionAc.java @@ -24,12 +24,18 @@ import android.net.LinkProperties; import android.net.ProxyProperties; import android.os.Message; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + /** * AsyncChannel to a DataConnection */ public class DataConnectionAc extends AsyncChannel { private static final boolean DBG = false; private String mLogTag; + private List<ApnContext> mApnList = null; public DataConnection dataConnection; @@ -85,6 +91,7 @@ public class DataConnectionAc extends AsyncChannel { public DataConnectionAc(DataConnection dc, String logTag) { dataConnection = dc; mLogTag = logTag; + mApnList = Collections.synchronizedList(new ArrayList<ApnContext>()); } /** @@ -371,6 +378,35 @@ public class DataConnectionAc extends AsyncChannel { } } + /** + * Add ApnContext association. + * + * @param ApnContext to associate + */ + public void addApnContext(ApnContext apnContext) { + if (!mApnList.contains(apnContext)) { + mApnList.add(apnContext); + } + } + + /** + * Remove ApnContext associateion. + * + * @param ApnContext to dissociate + */ + public void removeApnContext(ApnContext apnContext) { + mApnList.remove(apnContext); + } + + /** + * Retrieve collection of ApnContext currently associated with the DataConnectionAc. + * + * @return Collection of ApnContext + */ + public Collection<ApnContext> getApnList() { + return mApnList; + } + private void log(String s) { android.util.Log.d(mLogTag, "DataConnectionAc " + s); } diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java index 76f1ab7e1cdd..c6ed4050a082 100644 --- a/telephony/java/com/android/internal/telephony/RIL.java +++ b/telephony/java/com/android/internal/telephony/RIL.java @@ -3009,6 +3009,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { } } else { dataCall.status = p.readInt(); + dataCall.suggestedRetryTime = p.readInt(); dataCall.cid = p.readInt(); dataCall.active = p.readInt(); dataCall.type = p.readString(); diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java index 6a95b674167a..a31b7041696c 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java @@ -129,6 +129,12 @@ public class CDMALTEPhone extends CDMAPhone { super.setSystemLocale(language, country, false); } + // return IMSI from USIM as subscriber ID. + @Override + public String getSubscriberId() { + return mIccRecords.getIMSI(); + } + @Override protected void log(String s) { if (DBG) diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 19c06f68d436..1ac012f042db 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -68,7 +68,9 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; +import java.util.List; import java.util.Map; import java.util.HashMap; @@ -844,9 +846,16 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { return result; } + private boolean dataConnectionNotInUse(DataConnectionAc dcac) { + for (ApnContext apnContext : mApnContexts.values()) { + if (apnContext.getDataConnectionAc() == dcac) return false; + } + return true; + } + private GsmDataConnection findFreeDataConnection() { for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) { - if (dcac.isInactiveSync()) { + if (dcac.isInactiveSync() && dataConnectionNotInUse(dcac)) { log("findFreeDataConnection: found free GsmDataConnection"); return (GsmDataConnection) dcac.dataConnection; } @@ -926,7 +935,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { if (refCount == 0) { configureRetry(dc, apnContext.getApnType()); } - apnContext.setDataConnectionAc(mDataConnectionAsyncChannels.get(dc.getDataConnectionId())); + apnContext.setDataConnectionAc(dcac); apnContext.setApnSetting(apn); apnContext.setDataConnection(dc); } @@ -969,6 +978,49 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } /** + * @param cid Connection id provided from RIL. + * @return DataConnectionAc associated with specified cid. + */ + private DataConnectionAc findDataConnectionAcByCid(int cid) { + for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) { + if (dcac.getCidSync() == cid) { + return dcac; + } + } + return null; + } + + /** + * @param dcacs Collection of DataConnectionAc reported from RIL. + * @return List of ApnContext whihc is connected, but does not present in + * data connection list reported from RIL. + */ + private List<ApnContext> findApnContextToClean(Collection<DataConnectionAc> dcacs) { + if (dcacs == null) return null; + + ArrayList<ApnContext> list = new ArrayList<ApnContext>(); + for (ApnContext apnContext : mApnContexts.values()) { + if (apnContext.getState() == State.CONNECTED) { + boolean found = false; + for (DataConnectionAc dcac : dcacs) { + if (dcac == apnContext.getDataConnectionAc()) { + // ApnContext holds the ref to dcac present in data call list. + found = true; + break; + } + } + if (!found) { + // ApnContext does not have dcan reorted in data call list. + if (DBG) log("onDataStateChanged(ar): Connected apn not found in the list (" + + apnContext.toString() + ")"); + list.add(apnContext); + } + } + } + return list; + } + + /** * @param ar is the result of RIL_REQUEST_DATA_CALL_LIST * or RIL_UNSOL_DATA_CALL_LIST_CHANGED */ @@ -985,91 +1037,103 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { if (DBG) log("onDataStateChanged(ar): exception; likely radio not available, ignore"); return; } + if (DBG) log("onDataStateChanged(ar): DataCallState size=" + dataCallStates.size()); - // Create a hash map to store the dataCallState of each call id + // Create a hash map to store the dataCallState of each DataConnectionAc // TODO: Depends on how frequent the DATA_CALL_LIST got updated, // may cache response to reduce comparison. - HashMap<Integer, DataCallState> response; - response = new HashMap<Integer, DataCallState>(); - if (DBG) log("onDataStateChanged(ar): DataCallState size=" + dataCallStates.size()); - for (DataCallState dc : dataCallStates) { - response.put(dc.cid, dc); - if (DBG) log("onDataStateChanged(ar): " + dc.cid + ": " + dc.toString()); + HashMap<DataCallState, DataConnectionAc> response; + response = new HashMap<DataCallState, DataConnectionAc>(); + for (DataCallState dataCallState : dataCallStates) { + DataConnectionAc dcac = findDataConnectionAcByCid(dataCallState.cid); + + if (dcac != null) response.put(dataCallState, dcac); } - // For each connected apn, check if there is a matched active - // data call state, which has the same link properties. - if (DBG) log(" ApnContext size=" + mApnContexts.values().size()); - for (ApnContext apnContext : mApnContexts.values()) { - if (DBG){ - log("onDataStateChanged(ar): " + apnContext.toString()); - if (apnContext.getDataConnection() != null) { - log("onDataStateChanged(ar): " + apnContext.getDataConnection().toString()); + // step1: Find a list of "connected" APN which does not have reference to + // calls listed in the Data Call List. + List<ApnContext> apnsToClear = findApnContextToClean(response.values()); + + // step2: Check status of each calls in Data Call List. + // Collect list of ApnContext associated with the data call if the link + // has to be cleared. + for (DataCallState newState : dataCallStates) { + DataConnectionAc dcac = response.get(newState); + + // no associated DataConnection found. Ignore. + if (dcac == null) continue; + + Collection<ApnContext> apns = dcac.getApnList(); + + // filter out ApnContext with "Connected" state. + ArrayList<ApnContext> connectedApns = new ArrayList<ApnContext>(); + for (ApnContext apnContext : apns) { + if ((apnContext != null) && + (apnContext.getState() == State.CONNECTED)) { + connectedApns.add(apnContext); } } - DataConnectionAc dcac = apnContext.getDataConnectionAc(); - if (dcac == null) { + + // No "Connected" ApnContext associated with this CID. Ignore. + if (connectedApns.isEmpty()) { continue; } - int connectionId = dcac.getCidSync(); - if (apnContext.getState() == State.CONNECTED) { - // The way things are supposed to work, the PDP list - // should not contain the CID after it disconnects. - // However, the way things really work, sometimes the PDP - // context is still listed with active = false, which - // makes it hard to distinguish an activating context from - // an activated-and-then de-activated one. - if (response.containsKey(connectionId)) { - DataCallState newState = response.get(connectionId); - if (DBG) log("onDataStateChanged(ar): Found ConnId=" + connectionId + if (DBG) log("onDataStateChanged(ar): Found ConnId=" + newState.cid + " newState=" + newState.toString()); - if (newState.active != 0) { - boolean resetConnection; - switch (dcac.updateLinkPropertiesDataCallStateSync(newState)) { - case NONE: - if (DBG) log("onDataStateChanged(ar): Found but no change, skip"); - resetConnection = false; - break; - case CHANGED: - if (DBG) log("onDataStateChanged(ar): Found and changed, notify"); - mPhone.notifyDataConnection(Phone.REASON_LINK_PROPERTIES_CHANGED, - apnContext.getApnType()); - // Temporary hack, at this time a transition from CDMA -> Global - // fails so we'll hope for the best and not reset the connection. - // @see bug/4455071 - if (SystemProperties.getBoolean("telephony.ignore-state-changes", - true)) { - log("onDataStateChanged(ar): STOPSHIP don't reset, continue"); - resetConnection = false; - } else { - // Things changed so reset connection, when hack is removed - // this is the normal path. - log("onDataStateChanged(ar): changed so resetting connection"); - resetConnection = true; - } - break; - case RESET: - default: - if (DBG) log("onDataStateChanged(ar): an error, reset connection"); - resetConnection = true; - break; - } - if (resetConnection == false) continue; + if (newState.active != 0) { + boolean resetConnection; + switch (dcac.updateLinkPropertiesDataCallStateSync(newState)) { + case NONE: + if (DBG) log("onDataStateChanged(ar): Found but no change, skip"); + resetConnection = false; + break; + case CHANGED: + for (ApnContext apnContext : connectedApns) { + if (DBG) log("onDataStateChanged(ar): Found and changed, notify (" + + apnContext.toString() + ")"); + mPhone.notifyDataConnection(Phone.REASON_LINK_PROPERTIES_CHANGED, + apnContext.getApnType()); } + // Temporary hack, at this time a transition from CDMA -> Global + // fails so we'll hope for the best and not reset the connection. + // @see bug/4455071 + if (SystemProperties.getBoolean("telephony.ignore-state-changes", + true)) { + log("onDataStateChanged(ar): STOPSHIP don't reset, continue"); + resetConnection = false; + } else { + // Things changed so reset connection, when hack is removed + // this is the normal path. + log("onDataStateChanged(ar): changed so resetting connection"); + resetConnection = true; + } + break; + case RESET: + default: + if (DBG) log("onDataStateChanged(ar): an error, reset connection"); + resetConnection = true; + break; } + if (resetConnection == false) continue; + } - if (DBG) log("onDataStateChanged(ar): reset connection."); + if (DBG) log("onDataStateChanged(ar): reset connection."); - // Add an event log when the network drops PDP - int cid = getCellLocationId(); - EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid, - TelephonyManager.getDefault().getNetworkType()); + apnsToClear.addAll(connectedApns); + } - cleanUpConnection(true, apnContext); - } + // step3: Clear apn connection if applicable. + if (!apnsToClear.isEmpty()) { + // Add an event log when the network drops PDP + int cid = getCellLocationId(); + EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid, + TelephonyManager.getDefault().getNetworkType()); } + for (ApnContext apnContext : apnsToClear) { + cleanUpConnection(true, apnContext); + } if (DBG) log("onDataStateChanged(ar): X"); } @@ -1332,7 +1396,8 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { return retry; } - private void reconnectAfterFail(FailCause lastFailCauseCode, ApnContext apnContext) { + private void reconnectAfterFail(FailCause lastFailCauseCode, + ApnContext apnContext, int retryOverride) { if (apnContext == null) { loge("reconnectAfterFail: apnContext == null, impossible"); return; @@ -1357,9 +1422,14 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } } - int nextReconnectDelay = apnContext.getDataConnection().getRetryTimer(); + // If retry needs to be backed off for specific case (determined by RIL/Modem) + // use the specified timer instead of pre-configured retry pattern. + int nextReconnectDelay = retryOverride; + if (nextReconnectDelay < 0) { + nextReconnectDelay = apnContext.getDataConnection().getRetryTimer(); + apnContext.getDataConnection().increaseRetryCount(); + } startAlarmForReconnect(nextReconnectDelay, apnContext); - apnContext.getDataConnection().increaseRetryCount(); if (!shouldPostNotification(lastFailCauseCode)) { if (DBG) { @@ -1664,7 +1734,13 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } } else { if (DBG) log("onDataSetupComplete: Not all permanent failures, retry"); - startDelayedRetry(cause, apnContext); + // check to see if retry should be overridden for this failure. + int retryOverride = -1; + if (ar.exception instanceof DataConnection.CallSetupException) { + retryOverride = + ((DataConnection.CallSetupException)ar.exception).getRetryOverride(); + } + startDelayedRetry(cause, apnContext, retryOverride); } } else { if (DBG) log("onDataSetupComplete: Try next APN"); @@ -1936,9 +2012,10 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { return result.toString(); } - private void startDelayedRetry(GsmDataConnection.FailCause cause, ApnContext apnContext) { + private void startDelayedRetry(GsmDataConnection.FailCause cause, + ApnContext apnContext, int retryOverride) { notifyNoData(cause, apnContext); - reconnectAfterFail(cause, apnContext); + reconnectAfterFail(cause, apnContext, retryOverride); } private void setPreferredApn(int pos) { diff --git a/tests/GridLayoutTest/res/layout/grid7.xml b/tests/GridLayoutTest/res/layout/grid7.xml index b97a00b91560..b9e58d718efe 100644 --- a/tests/GridLayoutTest/res/layout/grid7.xml +++ b/tests/GridLayoutTest/res/layout/grid7.xml @@ -15,56 +15,26 @@ <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" - - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <LinearLayout - android:orientation="horizontal" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="10dip" > - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingRight="5dip" - android:text="flabe" /> - - <Button android:id="@+id/initialActivity" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="bax" /> - </LinearLayout> - - <LinearLayout - android:orientation="horizontal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="5dip" > - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingRight="5dip" - android:text="bar" /> - - <EditText android:id="@+id/numberOfEvents" - android:layout_marginLeft="2dip" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@android:drawable/editbox_background" - android:numeric="integer" - android:scrollHorizontally="true" - android:maxLines="1" /> - </LinearLayout> - - <Button android:id="@+id/start" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingTop="10dip" - android:text="Foo" /> + android:layout_height="match_parent"> + android:columnCount="2" + <Space + android:layout_row="0" + android:layout_column="0" + android:layout_width="109dip" + android:layout_height="108dip"/> + + <Button + android:text="Button 1" + android:layout_row="0" + android:layout_column="1" + /> + + <Button + android:text="Button 2" + android:layout_row="1" + android:layout_column="1" + /> </GridLayout> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java index 9bb5ba8e7aaf..723f3e8e778e 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java @@ -85,6 +85,10 @@ public class GLTextureViewActivity extends Activity implements TextureView.Surfa } } + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + } + private static class RenderThread extends Thread { private static final String LOG_TAG = "GLTextureView"; diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java index 2e23aaa7b675..f420fa06e720 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java @@ -100,4 +100,9 @@ public class GetBitmapActivity extends Activity implements TextureView.SurfaceTe mCamera.stopPreview(); mCamera.release(); } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + // Ignored + } } diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java index fa2e39a4ed7c..01ee90a368d1 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java @@ -18,7 +18,6 @@ package com.android.test.hwui; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; import android.app.Activity; import android.graphics.SurfaceTexture; import android.hardware.Camera; @@ -55,7 +54,7 @@ public class TextureViewActivity extends Activity implements TextureView.Surface @Override public void onClick(View v) { if (mAdded) { - mAnimatorSet.cancel(); + if (mAnimatorSet != null) mAnimatorSet.cancel(); mContent.removeView(mTextureView); } else { mContent.addView(mTextureView); @@ -85,25 +84,19 @@ public class TextureViewActivity extends Activity implements TextureView.Surface mTextureView.setCameraDistance(5000); - ObjectAnimator rotationY = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f); - rotationY.setRepeatMode(ObjectAnimator.REVERSE); - rotationY.setRepeatCount(ObjectAnimator.INFINITE); - rotationY.setDuration(4000); - rotationY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - ((View) mTextureView.getParent()).invalidate(); - } - }); +// ObjectAnimator rotationY = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f); +// rotationY.setRepeatMode(ObjectAnimator.REVERSE); +// rotationY.setRepeatCount(ObjectAnimator.INFINITE); +// rotationY.setDuration(4000); - ObjectAnimator alpha = ObjectAnimator.ofFloat(mTextureView, "alpha", 1.0f, 0.0f); - alpha.setRepeatMode(ObjectAnimator.REVERSE); - alpha.setRepeatCount(ObjectAnimator.INFINITE); - alpha.setDuration(4000); +// ObjectAnimator alpha = ObjectAnimator.ofFloat(mTextureView, "alpha", 1.0f, 0.0f); +// alpha.setRepeatMode(ObjectAnimator.REVERSE); +// alpha.setRepeatCount(ObjectAnimator.INFINITE); +// alpha.setDuration(4000); - mAnimatorSet = new AnimatorSet(); - mAnimatorSet.play(alpha).with(rotationY); - mAnimatorSet.start(); +// mAnimatorSet = new AnimatorSet(); +// mAnimatorSet.play(alpha).with(rotationY); +// mAnimatorSet.start(); } @Override @@ -116,4 +109,9 @@ public class TextureViewActivity extends Activity implements TextureView.Surface mCamera.stopPreview(); mCamera.release(); } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + // Ignored + } } diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java index 3f57799a8118..c706286df685 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java @@ -100,6 +100,7 @@ public class RsBenchRS { private ProgramVertex mProgVertex; private ProgramVertexFixedFunction.Constants mPVA; + private ProgramVertexFixedFunction.Constants mPvProjectionAlloc; // Custom shaders private ProgramVertex mProgVertexCustom; @@ -388,6 +389,7 @@ public class RsBenchRS { i.Proj = projNorm; i.MVP = projNorm; mPvStarAlloc.set(i, 0, true); + mPvProjectionAlloc.setProjection(projNorm); } private void initProgramVertex() { @@ -405,8 +407,14 @@ public class RsBenchRS { // For galaxy live wallpaper mPvStarAlloc = new ScriptField_VpConsts(mRS, 1); mScript.bind_vpConstants(mPvStarAlloc); + mPvProjectionAlloc = new ProgramVertexFixedFunction.Constants(mRS); updateProjectionMatrices(); + pvb = new ProgramVertexFixedFunction.Builder(mRS); + ProgramVertex pvbp = pvb.create(); + ((ProgramVertexFixedFunction)pvbp).bindConstants(mPvProjectionAlloc); + mScript.set_gPVBkProj(pvbp); + ProgramVertex.Builder sb = new ProgramVertex.Builder(mRS); String t = "varying vec4 varColor;\n" + "varying vec2 varTex0;\n" + @@ -648,10 +656,10 @@ public class RsBenchRS { prepareTestData(); initSamplers(); - initProgramStore(); - initProgramFragment(); initMesh(); initProgramVertex(); + initProgramStore(); + initProgramFragment(); initFonts(); loadImages(); initProgramRaster(); @@ -674,7 +682,6 @@ public class RsBenchRS { Allocation.USAGE_GRAPHICS_RENDER_TARGET); mScript.set_gRenderBufferDepth(offscreen); - mTextureAllocs = new ScriptField_ListAllocs_s(mRS, 100); for (int i = 0; i < 100; i++) { ScriptField_ListAllocs_s.Item texElem = new ScriptField_ListAllocs_s.Item(); diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs index 6d80b0ec5403..bb81862e4e30 100644 --- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs +++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs @@ -393,19 +393,28 @@ static void displayIcons(int meshMode) { rsgBindProgramStore(gProgStoreBlendAlpha); rsgBindProgramFragment(gProgFragmentTexture); rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp); + rsgBindTexture(gProgFragmentTexture, 0, gTexTorus); + rsgDrawQuadTexCoords( + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, gRenderSurfaceH, 0.0f, 0.0f, 1.0f, + gRenderSurfaceW, gRenderSurfaceH, 0.0f, 1.0f, 1.0f, + gRenderSurfaceW, 0.0f, 0.0f, 1.0f, 0.0f); int meshCount = (int)pow(10.0f, (float)(meshMode + 1)); - float size = 50.0; + float wSize = gRenderSurfaceW/(float)meshCount; + float hSize = gRenderSurfaceH/(float)meshCount; rs_matrix4x4 matrix; - rsMatrixLoadScale(&matrix, size, size, 1.0); + rsMatrixLoadScale(&matrix, wSize, hSize, 1.0); float yPos = 0; + float yPad = hSize / 2; + float xPad = wSize / 2; for (int y = 0; y < meshCount; y++) { - yPos = (y + 1) * 50; + yPos = y * hSize + yPad; float xPos = 0; for (int x = 0; x < meshCount; x++) { - xPos = (x + 1) * 50; + xPos = x * wSize + xPad; rs_matrix4x4 transMatrix; rsMatrixLoadTranslate(&transMatrix, xPos, yPos, 0); rsMatrixMultiply(&transMatrix, &matrix); @@ -529,11 +538,12 @@ static void drawGalaxy() { } // Display images and text with live wallpaper in the background -static void dispalyLiveWallPaper(int wResolution, int hResolution) { +static void displayLiveWallPaper(int wResolution, int hResolution) { bindProgramVertexOrtho(); drawGalaxy(); + rsgBindProgramVertex(gProgVertex); rsgBindProgramStore(gProgStoreBlendAlpha); rsgBindProgramFragment(gProgFragmentTexture); rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp); @@ -958,7 +968,7 @@ static void runTest(int index) { displayListView(); break; case 30: - dispalyLiveWallPaper(7, 5); + displayLiveWallPaper(7, 5); break; } } diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java index a45e8793d146..fb2fc8545338 100644 --- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java +++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java @@ -190,7 +190,6 @@ public class ICU_Delegate { // Used by DecimalFormatSymbols. result.zeroDigit = '0'; - result.digit = '0'; result.decimalSeparator = '.'; result.groupingSeparator = ','; result.patternSeparator = ' '; diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java index 4ee86b6e58cb..018e6de5c31b 100644 --- a/voip/java/com/android/server/sip/SipHelper.java +++ b/voip/java/com/android/server/sip/SipHelper.java @@ -71,6 +71,7 @@ import javax.sip.message.Response; class SipHelper { private static final String TAG = SipHelper.class.getSimpleName(); private static final boolean DEBUG = true; + private static final boolean DEBUG_PING = false; private SipStack mSipStack; private SipProvider mSipProvider; @@ -177,17 +178,19 @@ class SipHelper { return uri; } - public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag) - throws SipException { + public ClientTransaction sendOptions(SipProfile caller, SipProfile callee, + String tag) throws SipException { try { - Request request = createRequest(Request.OPTIONS, userProfile, tag); + Request request = (caller == callee) + ? createRequest(Request.OPTIONS, caller, tag) + : createRequest(Request.OPTIONS, caller, callee, tag); ClientTransaction clientTransaction = mSipProvider.getNewClientTransaction(request); clientTransaction.sendRequest(); return clientTransaction; } catch (Exception e) { - throw new SipException("sendKeepAlive()", e); + throw new SipException("sendOptions()", e); } } @@ -249,23 +252,29 @@ class SipHelper { return ct; } + private Request createRequest(String requestType, SipProfile caller, + SipProfile callee, String tag) throws ParseException, SipException { + FromHeader fromHeader = createFromHeader(caller, tag); + ToHeader toHeader = createToHeader(callee); + SipURI requestURI = callee.getUri(); + List<ViaHeader> viaHeaders = createViaHeaders(); + CallIdHeader callIdHeader = createCallIdHeader(); + CSeqHeader cSeqHeader = createCSeqHeader(requestType); + MaxForwardsHeader maxForwards = createMaxForwardsHeader(); + + Request request = mMessageFactory.createRequest(requestURI, + requestType, callIdHeader, cSeqHeader, fromHeader, + toHeader, viaHeaders, maxForwards); + + request.addHeader(createContactHeader(caller)); + return request; + } + public ClientTransaction sendInvite(SipProfile caller, SipProfile callee, String sessionDescription, String tag) throws SipException { try { - FromHeader fromHeader = createFromHeader(caller, tag); - ToHeader toHeader = createToHeader(callee); - SipURI requestURI = callee.getUri(); - List<ViaHeader> viaHeaders = createViaHeaders(); - CallIdHeader callIdHeader = createCallIdHeader(); - CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE); - MaxForwardsHeader maxForwards = createMaxForwardsHeader(); - - Request request = mMessageFactory.createRequest(requestURI, - Request.INVITE, callIdHeader, cSeqHeader, fromHeader, - toHeader, viaHeaders, maxForwards); - - request.addHeader(createContactHeader(caller)); + Request request = createRequest(Request.INVITE, caller, callee, tag); request.setContent(sessionDescription, mHeaderFactory.createContentTypeHeader( "application", "sdp")); @@ -419,9 +428,13 @@ class SipHelper { public void sendResponse(RequestEvent event, int responseCode) throws SipException { try { + Request request = event.getRequest(); Response response = mMessageFactory.createResponse( - responseCode, event.getRequest()); - if (DEBUG) Log.d(TAG, "send response: " + response); + responseCode, request); + if (DEBUG && (!Request.OPTIONS.equals(request.getMethod()) + || DEBUG_PING)) { + Log.d(TAG, "send response: " + response); + } getServerTransaction(event).sendResponse(response); } catch (ParseException e) { throw new SipException("sendResponse()", e); diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java index 5ad5d26aee92..f8e5b3a383e5 100644 --- a/voip/java/com/android/server/sip/SipService.java +++ b/voip/java/com/android/server/sip/SipService.java @@ -69,10 +69,11 @@ import javax.sip.SipException; public final class SipService extends ISipService.Stub { static final String TAG = "SipService"; static final boolean DEBUGV = false; - static final boolean DEBUG = false; + static final boolean DEBUG = true; private static final int EXPIRY_TIME = 3600; private static final int SHORT_EXPIRY_TIME = 10; private static final int MIN_EXPIRY_TIME = 60; + private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds private Context mContext; private String mLocalIp; @@ -378,7 +379,7 @@ public final class SipService extends ISipService.Stub { private void grabWifiLock() { if (mWifiLock == null) { - if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ acquire wifi lock"); + if (DEBUG) Log.d(TAG, "acquire wifi lock"); mWifiLock = ((WifiManager) mContext.getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); @@ -389,7 +390,7 @@ public final class SipService extends ISipService.Stub { private void releaseWifiLock() { if (mWifiLock != null) { - if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ release wifi lock"); + if (DEBUG) Log.d(TAG, "release wifi lock"); mWifiLock.release(); mWifiLock = null; stopWifiScanner(); @@ -459,9 +460,17 @@ public final class SipService extends ISipService.Stub { } } - private void startPortMappingLifetimeMeasurement(SipSessionGroup group) { - mIntervalMeasurementProcess = new IntervalMeasurementProcess(group); - mIntervalMeasurementProcess.start(); + private void startPortMappingLifetimeMeasurement( + SipProfile localProfile) { + if ((mIntervalMeasurementProcess == null) + && (mKeepAliveInterval == -1) + && isBehindNAT(mLocalIp)) { + Log.d(TAG, "start NAT port mapping timeout measurement on " + + localProfile.getUriString()); + + mIntervalMeasurementProcess = new IntervalMeasurementProcess(localProfile); + mIntervalMeasurementProcess.start(); + } } private synchronized void addPendingSession(ISipSession session) { @@ -500,6 +509,33 @@ public final class SipService extends ISipService.Stub { return false; } + private synchronized void onKeepAliveIntervalChanged() { + for (SipSessionGroupExt group : mSipGroups.values()) { + group.onKeepAliveIntervalChanged(); + } + } + + private int getKeepAliveInterval() { + return (mKeepAliveInterval < 0) + ? DEFAULT_KEEPALIVE_INTERVAL + : mKeepAliveInterval; + } + + private boolean isBehindNAT(String address) { + try { + byte[] d = InetAddress.getByName(address).getAddress(); + if ((d[0] == 10) || + (((0x000000FF & ((int)d[0])) == 172) && + ((0x000000F0 & ((int)d[1])) == 16)) || + (((0x000000FF & ((int)d[0])) == 192) && + ((0x000000FF & ((int)d[1])) == 168))) { + return true; + } + } catch (UnknownHostException e) { + Log.e(TAG, "isBehindAT()" + address, e); + } + return false; + } private class SipSessionGroupExt extends SipSessionAdapter { private SipSessionGroup mSipGroup; @@ -527,6 +563,16 @@ public final class SipService extends ISipService.Stub { return mSipGroup.containsSession(callId); } + public void onKeepAliveIntervalChanged() { + mAutoRegistration.onKeepAliveIntervalChanged(); + } + + // TODO: remove this method once SipWakeupTimer can better handle variety + // of timeout values + void setWakeupTimer(SipWakeupTimer timer) { + mSipGroup.setWakeupTimer(timer); + } + // network connectivity is tricky because network can be disconnected // at any instant so need to deal with exceptions carefully even when // you think you are connected @@ -534,7 +580,7 @@ public final class SipService extends ISipService.Stub { SipProfile localProfile, String password) throws SipException { try { return new SipSessionGroup(localIp, localProfile, password, - mMyWakeLock); + mTimer, mMyWakeLock); } catch (IOException e) { // network disconnected Log.w(TAG, "createSipSessionGroup(): network disconnected?"); @@ -697,158 +743,114 @@ public final class SipService extends ISipService.Stub { } } - private class IntervalMeasurementProcess extends SipSessionAdapter - implements Runnable { - private static final String TAG = "\\INTERVAL/"; + private class IntervalMeasurementProcess implements + SipSessionGroup.KeepAliveProcessCallback { + private static final String TAG = "SipKeepAliveInterval"; private static final int MAX_INTERVAL = 120; // seconds private static final int MIN_INTERVAL = SHORT_EXPIRY_TIME; - private static final int PASS_THRESHOLD = 6; + private static final int PASS_THRESHOLD = 10; private SipSessionGroupExt mGroup; private SipSessionGroup.SipSessionImpl mSession; private boolean mRunning; - private int mMinInterval = 10; + private int mMinInterval = 10; // in seconds private int mMaxInterval = MAX_INTERVAL; private int mInterval = MAX_INTERVAL / 2; private int mPassCounter = 0; - private SipWakeupTimer mTimer = new SipWakeupTimer(mContext, mExecutor); - // TODO: fix SipWakeupTimer so that we only use one instance of the timer - public IntervalMeasurementProcess(SipSessionGroup group) { + public IntervalMeasurementProcess(SipProfile localProfile) { try { - mGroup = new SipSessionGroupExt( - group.getLocalProfile(), null, null); + mGroup = new SipSessionGroupExt(localProfile, null, null); + // TODO: remove this line once SipWakeupTimer can better handle + // variety of timeout values + mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor)); mSession = (SipSessionGroup.SipSessionImpl) - mGroup.createSession(this); + mGroup.createSession(null); } catch (Exception e) { Log.w(TAG, "start interval measurement error: " + e); } } public void start() { - if (mRunning) return; - mRunning = true; - mTimer.set(mInterval * 1000, this); - if (DEBUGV) Log.v(TAG, "start interval measurement"); - run(); + synchronized (SipService.this) { + try { + mSession.startKeepAliveProcess(mInterval, this); + } catch (SipException e) { + Log.e(TAG, "start()", e); + } + } } public void stop() { - mRunning = false; - mTimer.cancel(this); - } - - private void restart() { - mTimer.cancel(this); - mTimer.set(mInterval * 1000, this); - } - - private void calculateNewInterval() { - if (!mSession.isReRegisterRequired()) { - if (++mPassCounter != PASS_THRESHOLD) return; - // update the interval, since the current interval is good to - // keep the port mapping. - mKeepAliveInterval = mMinInterval = mInterval; - } else { - // Since the rport is changed, shorten the interval. - mSession.clearReRegisterRequired(); - mMaxInterval = mInterval; - } - if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) { - // update mKeepAliveInterval and stop measurement. - stop(); - mKeepAliveInterval = mMinInterval; - if (DEBUGV) Log.v(TAG, "measured interval: " + mKeepAliveInterval); - } else { - // calculate the new interval and continue. - mInterval = (mMaxInterval + mMinInterval) / 2; - mPassCounter = 0; - if (DEBUGV) { - Log.v(TAG, " current interval: " + mKeepAliveInterval - + "test new interval: " + mInterval); - } - restart(); + synchronized (SipService.this) { + mSession.stopKeepAliveProcess(); } } - public void run() { + private void restart() { synchronized (SipService.this) { - if (!mRunning) return; try { - mSession.sendKeepAlive(); - calculateNewInterval(); - } catch (Throwable t) { - stop(); - Log.w(TAG, "interval measurement error: " + t); + mSession.stopKeepAliveProcess(); + mSession.startKeepAliveProcess(mInterval, this); + } catch (SipException e) { + Log.e(TAG, "restart()", e); } } } - } - - // KeepAliveProcess is controlled by AutoRegistrationProcess. - // All methods will be invoked in sync with SipService.this. - private class KeepAliveProcess implements Runnable { - private static final String TAG = "\\KEEPALIVE/"; - private static final int INTERVAL = 10; - private SipSessionGroup.SipSessionImpl mSession; - private boolean mRunning = false; - private int mInterval = INTERVAL; - - public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { - mSession = session; - } - public void start() { - if (mRunning) return; - mRunning = true; - mTimer.set(INTERVAL * 1000, this); - } - - private void restart(int duration) { - if (DEBUG) Log.d(TAG, "Refresh NAT port mapping " + duration + "s later."); - mTimer.cancel(this); - mTimer.set(duration * 1000, this); - } - - // timeout handler - public void run() { + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onResponse(boolean portChanged) { synchronized (SipService.this) { - if (!mRunning) return; - - if (DEBUGV) Log.v(TAG, "~~~ keepalive: " - + mSession.getLocalProfile().getUriString()); - SipSessionGroup.SipSessionImpl session = mSession.duplicate(); - try { - session.sendKeepAlive(); - if (session.isReRegisterRequired()) { - // Acquire wake lock for the registration process. The - // lock will be released when registration is complete. - mMyWakeLock.acquire(mSession); - mSession.register(EXPIRY_TIME); + if (!portChanged) { + if (++mPassCounter != PASS_THRESHOLD) return; + // update the interval, since the current interval is good to + // keep the port mapping. + mKeepAliveInterval = mMinInterval = mInterval; + if (DEBUG) { + Log.d(TAG, "measured good keepalive interval: " + + mKeepAliveInterval); + } + onKeepAliveIntervalChanged(); + } else { + // Since the rport is changed, shorten the interval. + mMaxInterval = mInterval; + } + if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) { + // update mKeepAliveInterval and stop measurement. + stop(); + mKeepAliveInterval = mMinInterval; + if (DEBUG) { + Log.d(TAG, "measured keepalive interval: " + + mKeepAliveInterval); } - if (mKeepAliveInterval > mInterval) { - mInterval = mKeepAliveInterval; - restart(mInterval); + } else { + // calculate the new interval and continue. + mInterval = (mMaxInterval + mMinInterval) / 2; + mPassCounter = 0; + if (DEBUG) { + Log.d(TAG, "current interval: " + mKeepAliveInterval + + ", test new interval: " + mInterval); } - } catch (Throwable t) { - Log.w(TAG, "keepalive error: " + t); + restart(); } } } - public void stop() { - if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:" - + mSession.getLocalProfile().getUriString()); - mRunning = false; - mSession = null; - mTimer.cancel(this); + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onError(int errorCode, String description) { + synchronized (SipService.this) { + Log.w(TAG, "interval measurement error: " + description); + } } } private class AutoRegistrationProcess extends SipSessionAdapter - implements Runnable { + implements Runnable, SipSessionGroup.KeepAliveProcessCallback { + private String TAG = "SipAudoReg"; private SipSessionGroup.SipSessionImpl mSession; + private SipSessionGroup.SipSessionImpl mKeepAliveSession; private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); - private KeepAliveProcess mKeepAliveProcess; private int mBackoff = 1; private boolean mRegistered; private long mExpiryTime; @@ -869,27 +871,44 @@ public final class SipService extends ISipService.Stub { // return right away if no active network connection. if (mSession == null) return; - synchronized (SipService.this) { - if (isBehindNAT(mLocalIp) - && (mIntervalMeasurementProcess == null) - && (mKeepAliveInterval == -1)) { - // Start keep-alive interval measurement, here we allow - // the first profile only as the target service provider - // to measure the life time of NAT port mapping. - startPortMappingLifetimeMeasurement(group); - } - } - // start unregistration to clear up old registration at server // TODO: when rfc5626 is deployed, use reg-id and sip.instance // in registration to avoid adding duplicate entries to server mMyWakeLock.acquire(mSession); mSession.unregister(); - if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " - + mSession.getLocalProfile().getUriString()); + if (DEBUG) TAG = mSession.getLocalProfile().getUriString(); + if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess"); } } + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onResponse(boolean portChanged) { + synchronized (SipService.this) { + // Start keep-alive interval measurement on the first successfully + // kept-alive SipSessionGroup + startPortMappingLifetimeMeasurement(mSession.getLocalProfile()); + + if (!mRunning || !portChanged) return; + + // The keep alive process is stopped when port is changed; + // Nullify the session so that the process can be restarted + // again when the re-registration is done + mKeepAliveSession = null; + + // Acquire wake lock for the registration process. The + // lock will be released when registration is complete. + mMyWakeLock.acquire(mSession); + mSession.register(EXPIRY_TIME); + } + } + + // SipSessionGroup.KeepAliveProcessCallback + @Override + public void onError(int errorCode, String description) { + Log.e(TAG, "keepalive error: " + description); + } + public void stop() { if (!mRunning) return; mRunning = false; @@ -900,15 +919,30 @@ public final class SipService extends ISipService.Stub { } mTimer.cancel(this); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; + if (mKeepAliveSession != null) { + mKeepAliveSession.stopKeepAliveProcess(); + mKeepAliveSession = null; } mRegistered = false; setListener(mProxy.getListener()); } + public void onKeepAliveIntervalChanged() { + if (mKeepAliveSession != null) { + int newInterval = getKeepAliveInterval(); + if (DEBUGV) { + Log.v(TAG, "restart keepalive w interval=" + newInterval); + } + mKeepAliveSession.stopKeepAliveProcess(); + try { + mKeepAliveSession.startKeepAliveProcess(newInterval, this); + } catch (SipException e) { + Log.e(TAG, "onKeepAliveIntervalChanged()", e); + } + } + } + public void setListener(ISipSessionListener listener) { synchronized (SipService.this) { mProxy.setListener(listener); @@ -955,13 +989,14 @@ public final class SipService extends ISipService.Stub { } // timeout handler: re-register + @Override public void run() { synchronized (SipService.this) { if (!mRunning) return; mErrorCode = SipErrorCode.NO_ERROR; mErrorMessage = null; - if (DEBUG) Log.d(TAG, "~~~ registering"); + if (DEBUG) Log.d(TAG, "registering"); if (mConnected) { mMyWakeLock.acquire(mSession); mSession.register(EXPIRY_TIME); @@ -969,22 +1004,6 @@ public final class SipService extends ISipService.Stub { } } - private boolean isBehindNAT(String address) { - try { - byte[] d = InetAddress.getByName(address).getAddress(); - if ((d[0] == 10) || - (((0x000000FF & ((int)d[0])) == 172) && - ((0x000000F0 & ((int)d[1])) == 16)) || - (((0x000000FF & ((int)d[0])) == 192) && - ((0x000000FF & ((int)d[1])) == 168))) { - return true; - } - } catch (UnknownHostException e) { - Log.e(TAG, "isBehindAT()" + address, e); - } - return false; - } - private void restart(int duration) { if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later."); mTimer.cancel(this); @@ -1030,7 +1049,6 @@ public final class SipService extends ISipService.Stub { mProxy.onRegistrationDone(session, duration); if (duration > 0) { - mSession.clearReRegisterRequired(); mExpiryTime = SystemClock.elapsedRealtime() + (duration * 1000); @@ -1043,13 +1061,17 @@ public final class SipService extends ISipService.Stub { } restart(duration); - if (isBehindNAT(mLocalIp) || - mSession.getLocalProfile().getSendKeepAlive()) { - if (mKeepAliveProcess == null) { - mKeepAliveProcess = - new KeepAliveProcess(mSession); + SipProfile localProfile = mSession.getLocalProfile(); + if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp) + || localProfile.getSendKeepAlive())) { + mKeepAliveSession = mSession.duplicate(); + Log.d(TAG, "start keepalive"); + try { + mKeepAliveSession.startKeepAliveProcess( + getKeepAliveInterval(), this); + } catch (SipException e) { + Log.e(TAG, "AutoRegistrationProcess", e); } - mKeepAliveProcess.start(); } } mMyWakeLock.release(session); @@ -1103,10 +1125,6 @@ public final class SipService extends ISipService.Stub { private void restartLater() { mRegistered = false; restart(backoffDuration()); - if (mKeepAliveProcess != null) { - mKeepAliveProcess.stop(); - mKeepAliveProcess = null; - } } } diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java index 4837eb908024..cc3e4109b138 100644 --- a/voip/java/com/android/server/sip/SipSessionGroup.java +++ b/voip/java/com/android/server/sip/SipSessionGroup.java @@ -28,6 +28,7 @@ import android.net.sip.ISipSessionListener; import android.net.sip.SipErrorCode; import android.net.sip.SipProfile; import android.net.sip.SipSession; +import android.net.sip.SipSessionAdapter; import android.text.TextUtils; import android.util.Log; @@ -89,6 +90,7 @@ class SipSessionGroup implements SipListener { private static final String THREAD_POOL_SIZE = "1"; private static final int EXPIRY_TIME = 3600; // in seconds private static final int CANCEL_CALL_TIMER = 3; // in seconds + private static final int KEEPALIVE_TIMEOUT = 3; // in seconds private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds private static final EventObject DEREGISTER = new EventObject("Deregister"); @@ -107,6 +109,7 @@ class SipSessionGroup implements SipListener { private SipSessionImpl mCallReceiverSession; private String mLocalIp; + private SipWakeupTimer mWakeupTimer; private SipWakeLock mWakeLock; // call-id-to-SipSession map @@ -119,13 +122,21 @@ class SipSessionGroup implements SipListener { * @throws IOException if cannot assign requested address */ public SipSessionGroup(String localIp, SipProfile myself, String password, - SipWakeLock wakeLock) throws SipException, IOException { + SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException, + IOException { mLocalProfile = myself; mPassword = password; + mWakeupTimer = timer; mWakeLock = wakeLock; reset(localIp); } + // TODO: remove this method once SipWakeupTimer can better handle variety + // of timeout values + void setWakeupTimer(SipWakeupTimer timer) { + mWakeupTimer = timer; + } + synchronized void reset(String localIp) throws SipException, IOException { mLocalIp = localIp; if (localIp == null) return; @@ -382,6 +393,12 @@ class SipSessionGroup implements SipListener { } } + static interface KeepAliveProcessCallback { + /** Invoked when the response of keeping alive comes back. */ + void onResponse(boolean portChanged); + void onError(int errorCode, String description); + } + class SipSessionImpl extends ISipSession.Stub { SipProfile mPeerProfile; SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); @@ -392,12 +409,10 @@ class SipSessionGroup implements SipListener { ClientTransaction mClientTransaction; String mPeerSessionDescription; boolean mInCall; - SessionTimer mTimer; + SessionTimer mSessionTimer; int mAuthenticationRetryCount; - // for registration - boolean mReRegisterFlag = false; - int mRPort = 0; + private KeepAliveProcess mKeepAliveProcess; // lightweight timer class SessionTimer { @@ -512,7 +527,9 @@ class SipSessionGroup implements SipListener { try { processCommand(command); } catch (Throwable e) { - Log.w(TAG, "command error: " + command, e); + Log.w(TAG, "command error: " + command + ": " + + mLocalProfile.getUriString(), + getRootCause(e)); onError(e); } } @@ -553,34 +570,6 @@ class SipSessionGroup implements SipListener { doCommandAsync(DEREGISTER); } - public boolean isReRegisterRequired() { - return mReRegisterFlag; - } - - public void clearReRegisterRequired() { - mReRegisterFlag = false; - } - - public void sendKeepAlive() { - mState = SipSession.State.PINGING; - try { - processCommand(new OptionsCommand()); - for (int i = 0; i < 15; i++) { - if (SipSession.State.PINGING != mState) break; - Thread.sleep(200); - } - if (SipSession.State.PINGING == mState) { - // FIXME: what to do if server doesn't respond - reset(); - if (DEBUG) Log.w(TAG, "no response from ping"); - } - } catch (SipException e) { - Log.e(TAG, "sendKeepAlive failed", e); - } catch (InterruptedException e) { - Log.e(TAG, "sendKeepAlive interrupted", e); - } - } - private void processCommand(EventObject command) throws SipException { if (isLoggable(command)) Log.d(TAG, "process cmd: " + command); if (!process(command)) { @@ -612,6 +601,11 @@ class SipSessionGroup implements SipListener { synchronized (SipSessionGroup.this) { if (isClosed()) return false; + if (mKeepAliveProcess != null) { + // event consumed by keepalive process + if (mKeepAliveProcess.process(evt)) return true; + } + Dialog dialog = null; if (evt instanceof RequestEvent) { dialog = ((RequestEvent) evt).getDialog(); @@ -627,9 +621,6 @@ class SipSessionGroup implements SipListener { case SipSession.State.DEREGISTERING: processed = registeringToReady(evt); break; - case SipSession.State.PINGING: - processed = keepAliveProcess(evt); - break; case SipSession.State.READY_TO_CALL: processed = readyForCall(evt); break; @@ -754,10 +745,6 @@ class SipSessionGroup implements SipListener { case SipSession.State.OUTGOING_CALL_CANCELING: onError(SipErrorCode.TIME_OUT, event.toString()); break; - case SipSession.State.PINGING: - reset(); - mReRegisterFlag = true; - break; default: Log.d(TAG, " do nothing"); @@ -778,48 +765,6 @@ class SipSessionGroup implements SipListener { return expires; } - private boolean keepAliveProcess(EventObject evt) throws SipException { - if (evt instanceof OptionsCommand) { - mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile, - generateTag()); - mDialog = mClientTransaction.getDialog(); - addSipSession(this); - return true; - } else if (evt instanceof ResponseEvent) { - return parseOptionsResult(evt); - } - return false; - } - - private boolean parseOptionsResult(EventObject evt) { - if (expectResponse(Request.OPTIONS, evt)) { - ResponseEvent event = (ResponseEvent) evt; - int rPort = getRPortFromResponse(event.getResponse()); - if (rPort != -1) { - if (mRPort == 0) mRPort = rPort; - if (mRPort != rPort) { - mReRegisterFlag = true; - if (DEBUG) Log.w(TAG, String.format( - "rport is changed: %d <> %d", mRPort, rPort)); - mRPort = rPort; - } else { - if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort); - } - } else { - if (DEBUG) Log.w(TAG, "peer did not respond rport"); - } - reset(); - return true; - } - return false; - } - - private int getRPortFromResponse(Response response) { - ViaHeader viaHeader = (ViaHeader)(response.getHeader( - SIPHeaderNames.VIA)); - return (viaHeader == null) ? -1 : viaHeader.getRPort(); - } - private boolean registeringToReady(EventObject evt) throws SipException { if (expectResponse(Request.REGISTER, evt)) { @@ -1138,15 +1083,15 @@ class SipSessionGroup implements SipListener { // timeout in seconds private void startSessionTimer(int timeout) { if (timeout > 0) { - mTimer = new SessionTimer(); - mTimer.start(timeout); + mSessionTimer = new SessionTimer(); + mSessionTimer.start(timeout); } } private void cancelSessionTimer() { - if (mTimer != null) { - mTimer.cancel(); - mTimer = null; + if (mSessionTimer != null) { + mSessionTimer.cancel(); + mSessionTimer = null; } } @@ -1272,6 +1217,168 @@ class SipSessionGroup implements SipListener { onRegistrationFailed(getErrorCode(statusCode), createErrorMessage(response)); } + + // Notes: SipSessionListener will be replaced by the keepalive process + // @param interval in seconds + public void startKeepAliveProcess(int interval, + KeepAliveProcessCallback callback) throws SipException { + synchronized (SipSessionGroup.this) { + startKeepAliveProcess(interval, mLocalProfile, callback); + } + } + + // Notes: SipSessionListener will be replaced by the keepalive process + // @param interval in seconds + public void startKeepAliveProcess(int interval, SipProfile peerProfile, + KeepAliveProcessCallback callback) throws SipException { + synchronized (SipSessionGroup.this) { + if (mKeepAliveProcess != null) { + throw new SipException("Cannot create more than one " + + "keepalive process in a SipSession"); + } + mPeerProfile = peerProfile; + mKeepAliveProcess = new KeepAliveProcess(); + mProxy.setListener(mKeepAliveProcess); + mKeepAliveProcess.start(interval, callback); + } + } + + public void stopKeepAliveProcess() { + synchronized (SipSessionGroup.this) { + if (mKeepAliveProcess != null) { + mKeepAliveProcess.stop(); + mKeepAliveProcess = null; + } + } + } + + class KeepAliveProcess extends SipSessionAdapter implements Runnable { + private static final String TAG = "SipKeepAlive"; + private boolean mRunning = false; + private KeepAliveProcessCallback mCallback; + + private boolean mPortChanged = false; + private int mRPort = 0; + + // @param interval in seconds + void start(int interval, KeepAliveProcessCallback callback) { + if (mRunning) return; + mRunning = true; + mCallback = new KeepAliveProcessCallbackProxy(callback); + mWakeupTimer.set(interval * 1000, this); + if (DEBUG) { + Log.d(TAG, "start keepalive:" + + mLocalProfile.getUriString()); + } + + // No need to run the first time in a separate thread for now + run(); + } + + // return true if the event is consumed + boolean process(EventObject evt) throws SipException { + if (mRunning && (mState == SipSession.State.PINGING)) { + if (evt instanceof ResponseEvent) { + if (parseOptionsResult(evt)) { + if (mPortChanged) { + stop(); + } else { + cancelSessionTimer(); + removeSipSession(SipSessionImpl.this); + } + mCallback.onResponse(mPortChanged); + return true; + } + } + } + return false; + } + + // SipSessionAdapter + // To react to the session timeout event and network error. + @Override + public void onError(ISipSession session, int errorCode, String message) { + stop(); + mCallback.onError(errorCode, message); + } + + // SipWakeupTimer timeout handler + // To send out keepalive message. + @Override + public void run() { + synchronized (SipSessionGroup.this) { + if (!mRunning) return; + + if (DEBUG_PING) { + Log.d(TAG, "keepalive: " + mLocalProfile.getUriString() + + " --> " + mPeerProfile); + } + try { + sendKeepAlive(); + } catch (Throwable t) { + Log.w(TAG, "keepalive error: " + ": " + + mLocalProfile.getUriString(), getRootCause(t)); + // It's possible that the keepalive process is being stopped + // during session.sendKeepAlive() so need to check mRunning + // again here. + if (mRunning) SipSessionImpl.this.onError(t); + } + } + } + + void stop() { + synchronized (SipSessionGroup.this) { + if (DEBUG) { + Log.d(TAG, "stop keepalive:" + mLocalProfile.getUriString() + + ",RPort=" + mRPort); + } + mRunning = false; + mWakeupTimer.cancel(this); + reset(); + } + } + + private void sendKeepAlive() throws SipException, InterruptedException { + synchronized (SipSessionGroup.this) { + mState = SipSession.State.PINGING; + mClientTransaction = mSipHelper.sendOptions( + mLocalProfile, mPeerProfile, generateTag()); + mDialog = mClientTransaction.getDialog(); + addSipSession(SipSessionImpl.this); + + startSessionTimer(KEEPALIVE_TIMEOUT); + // when timed out, onError() will be called with SipErrorCode.TIME_OUT + } + } + + private boolean parseOptionsResult(EventObject evt) { + if (expectResponse(Request.OPTIONS, evt)) { + ResponseEvent event = (ResponseEvent) evt; + int rPort = getRPortFromResponse(event.getResponse()); + if (rPort != -1) { + if (mRPort == 0) mRPort = rPort; + if (mRPort != rPort) { + mPortChanged = true; + if (DEBUG) Log.d(TAG, String.format( + "rport is changed: %d <> %d", mRPort, rPort)); + mRPort = rPort; + } else { + if (DEBUG) Log.d(TAG, "rport is the same: " + rPort); + } + } else { + if (DEBUG) Log.w(TAG, "peer did not respond rport"); + } + return true; + } + return false; + } + + private int getRPortFromResponse(Response response) { + ViaHeader viaHeader = (ViaHeader)(response.getHeader( + SIPHeaderNames.VIA)); + return (viaHeader == null) ? -1 : viaHeader.getRPort(); + } + } } /** @@ -1363,15 +1470,16 @@ class SipSessionGroup implements SipListener { if (!isLoggable(s)) return false; if (evt == null) return false; - if (evt instanceof OptionsCommand) { - return DEBUG_PING; - } else if (evt instanceof ResponseEvent) { + if (evt instanceof ResponseEvent) { Response response = ((ResponseEvent) evt).getResponse(); if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) { return DEBUG_PING; } return DEBUG; } else if (evt instanceof RequestEvent) { + if (isRequestEvent(Request.OPTIONS, evt)) { + return DEBUG_PING; + } return DEBUG; } return false; @@ -1387,12 +1495,6 @@ class SipSessionGroup implements SipListener { } } - private class OptionsCommand extends EventObject { - public OptionsCommand() { - super(SipSessionGroup.this); - } - } - private class RegisterCommand extends EventObject { private int mDuration; @@ -1434,4 +1536,46 @@ class SipSessionGroup implements SipListener { return mTimeout; } } + + /** Class to help safely run KeepAliveProcessCallback in a different thread. */ + static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback { + private KeepAliveProcessCallback mCallback; + + KeepAliveProcessCallbackProxy(KeepAliveProcessCallback callback) { + mCallback = callback; + } + + private void proxy(Runnable runnable) { + // One thread for each calling back. + // Note: Guarantee ordering if the issue becomes important. Currently, + // the chance of handling two callback events at a time is none. + new Thread(runnable, "SIP-KeepAliveProcessCallbackThread").start(); + } + + public void onResponse(final boolean portChanged) { + if (mCallback == null) return; + proxy(new Runnable() { + public void run() { + try { + mCallback.onResponse(portChanged); + } catch (Throwable t) { + Log.w(TAG, "onResponse", t); + } + } + }); + } + + public void onError(final int errorCode, final String description) { + if (mCallback == null) return; + proxy(new Runnable() { + public void run() { + try { + mCallback.onError(errorCode, description); + } catch (Throwable t) { + Log.w(TAG, "onError", t); + } + } + }); + } + } } |