diff options
24 files changed, 623 insertions, 169 deletions
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index e56e257..6d19f41 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -68,6 +68,7 @@ public class RecoverySystem { private static File RECOVERY_DIR = new File("/cache/recovery"); private static File COMMAND_FILE = new File(RECOVERY_DIR, "command"); private static File LOG_FILE = new File(RECOVERY_DIR, "log"); + private static String LAST_LOG_FILENAME = "last_log"; // Length limits for reading files. private static int LOG_FILE_MAX_LENGTH = 64 * 1024; @@ -399,9 +400,10 @@ public class RecoverySystem { Log.e(TAG, "Error reading recovery log", e); } - // Delete everything in RECOVERY_DIR + // Delete everything in RECOVERY_DIR except LAST_LOG_FILENAME String[] names = RECOVERY_DIR.list(); for (int i = 0; names != null && i < names.length; i++) { + if (names[i].equals(LAST_LOG_FILENAME)) continue; File f = new File(RECOVERY_DIR, names[i]); if (!f.delete()) { Log.e(TAG, "Can't delete: " + f); diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index e05fe7b..bcb151a 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -225,10 +225,10 @@ class BluetoothEventLoop { } String name = propValues[0]; if (name.equals("Name")) { + mBluetoothService.setProperty(name, propValues[1]); Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]); mContext.sendBroadcast(intent, BLUETOOTH_PERM); - mBluetoothService.setProperty(name, propValues[1]); } else if (name.equals("Pairable") || name.equals("Discoverable")) { String pairable = name.equals("Pairable") ? propValues[1] : mBluetoothService.getPropertyInternal("Pairable"); @@ -239,6 +239,7 @@ class BluetoothEventLoop { if (pairable == null || discoverable == null) return; + mBluetoothService.setProperty(name, propValues[1]); int mode = BluetoothService.bluezStringToScanMode( pairable.equals("true"), discoverable.equals("true")); @@ -248,9 +249,9 @@ class BluetoothEventLoop { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendBroadcast(intent, BLUETOOTH_PERM); } - mBluetoothService.setProperty(name, propValues[1]); } else if (name.equals("Discovering")) { Intent intent; + mBluetoothService.setProperty(name, propValues[1]); if (propValues[1].equals("true")) { mBluetoothService.setIsDiscovering(true); intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED); @@ -261,7 +262,6 @@ class BluetoothEventLoop { intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); } mContext.sendBroadcast(intent, BLUETOOTH_PERM); - mBluetoothService.setProperty(name, propValues[1]); } else if (name.equals("Devices")) { String value = null; int len = Integer.valueOf(propValues[1]); @@ -294,19 +294,20 @@ class BluetoothEventLoop { } BluetoothDevice device = mAdapter.getRemoteDevice(address); if (name.equals("Name")) { + mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]); mContext.sendBroadcast(intent, BLUETOOTH_PERM); - mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); } else if (name.equals("Class")) { + mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothDevice.EXTRA_CLASS, new BluetoothClass(Integer.valueOf(propValues[1]))); mContext.sendBroadcast(intent, BLUETOOTH_PERM); - mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); } else if (name.equals("Connected")) { + mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); Intent intent = null; if (propValues[1].equals("true")) { intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); @@ -320,7 +321,6 @@ class BluetoothEventLoop { } intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); mContext.sendBroadcast(intent, BLUETOOTH_PERM); - mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); } else if (name.equals("UUIDs")) { String uuid = null; int len = Integer.valueOf(propValues[1]); diff --git a/core/res/res/drawable-hdpi/overscroll_edge.png b/core/res/res/drawable-hdpi/overscroll_edge.png Binary files differindex f8e40ec..e8c1aa3 100644 --- a/core/res/res/drawable-hdpi/overscroll_edge.png +++ b/core/res/res/drawable-hdpi/overscroll_edge.png diff --git a/core/res/res/drawable-hdpi/overscroll_glow.png b/core/res/res/drawable-hdpi/overscroll_glow.png Binary files differindex a8a62c4..3418384 100644 --- a/core/res/res/drawable-hdpi/overscroll_glow.png +++ b/core/res/res/drawable-hdpi/overscroll_glow.png diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7832f83..d9177e7 100644..100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -381,8 +381,10 @@ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> <string name="permgrouplab_storage">Storage</string> + <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <string name="permgroupdesc_storage" product="nosdcard">Access the shared storage.</string> <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permgroupdesc_storage">Access the SD card.</string> + <string name="permgroupdesc_storage" product="default">Access the SD card.</string> <!-- Permissions --> @@ -1224,10 +1226,14 @@ <string name="permdesc_writeDictionary">Allows an application to write new words into the user dictionary.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <string name="permlab_sdcardWrite" product="nosdcard">modify/delete shared storage contents</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_sdcardWrite">modify/delete SD card contents</string> + <string name="permlab_sdcardWrite" product="default">modify/delete SD card contents</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=30] --> + <string name="permdesc_sdcardWrite" product="nosdcard">Allows an application to write to the shared storage.</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_sdcardWrite">Allows an application to write to the SD card.</string> + <string name="permdesc_sdcardWrite" product="default">Allows an application to write to the SD card.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_cache_filesystem">access the cache filesystem</string> @@ -1237,31 +1243,28 @@ <!-- Policy administration --> <!-- Title of policy access to limiting the user's password choices --> - <string name="policylab_limitPassword">Limit password</string> + <string name="policylab_limitPassword">Set password rules</string> <!-- Description of policy access to limiting the user's password choices --> - <string name="policydesc_limitPassword">Restrict the types of passwords you - are allowed to use.</string> + <string name="policydesc_limitPassword">Control the length and the characters allowed in + screen-unlock passwords</string> <!-- Title of policy access to watch user login attempts --> - <string name="policylab_watchLogin">Watch login attempts</string> + <string name="policylab_watchLogin">Monitor screen-unlock attempts</string> <!-- Description of policy access to watch user login attempts --> - <string name="policydesc_watchLogin">Monitor failed attempts to login to - the device, to perform some action.</string> + <string name="policydesc_watchLogin">Monitor the number of incorrect passwords entered when unlocking + the screen, and lock the phone or erase all the phone\'s data if too many incorrect passwords are entered + </string> <!-- Title of policy access to reset user's password --> - <string name="policylab_resetPassword">Reset password</string> + <string name="policylab_resetPassword">Change the screen-unlock password</string> <!-- Description of policy access to reset user's password --> - <string name="policydesc_resetPassword">Force your password - to a new value, requiring the administrator give it to you - before you can log in.</string> + <string name="policydesc_resetPassword">Change the screen-unlock password</string> <!-- Title of policy access to force lock the device --> - <string name="policylab_forceLock">Force lock</string> + <string name="policylab_forceLock">Lock the screen</string> <!-- Description of policy access to limiting the user's password choices --> - <string name="policydesc_forceLock">Control when device locks, - requiring you re-enter its password.</string> + <string name="policydesc_forceLock">Control how and when the screen locks</string> <!-- Title of policy access to wipe the user's data --> <string name="policylab_wipeData">Erase all data</string> <!-- Description of policy access to wipe the user's data --> - <string name="policydesc_wipeData">Perform a factory reset, deleting - all of your data without any confirmation.</string> + <string name="policydesc_wipeData">Erase the phone\'s data without warning, by performing a factory data reset</string> <!-- The order of these is important, don't reorder without changing Contacts.java --> <skip /> <!-- Phone number types from android.provider.Contacts. This could be used when adding a new phone number for a contact, for example. --> @@ -2082,12 +2085,16 @@ <!-- See USB_STORAGE. USB_STORAGE_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to mount. This is the title. --> <string name="usb_storage_title">USB connected</string> + <!-- See USB_STORAGE. This is the message. [CHAR LIMIT=NONE] --> + <string name="usb_storage_message" product="nosdcard">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s shared storage.</string> <!-- See USB_STORAGE. This is the message. --> - <string name="usb_storage_message">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s SD card.</string> + <string name="usb_storage_message" product="default">You have connected your phone to your computer via USB. Select the button below if you want to copy files between your computer and your Android\u2018s SD card.</string> <!-- See USB_STORAGE. This is the button text to mount the phone on the computer. --> <string name="usb_storage_button_mount">Turn on USB storage</string> + <!-- See USB_STORAGE_DIALOG. If there was an error mounting, this is the text. [CHAR LIMIT=NONE] --> + <string name="usb_storage_error_message" product="nosdcard">There is a problem using your shared storage for USB storage.</string> <!-- See USB_STORAGE_DIALOG. If there was an error mounting, this is the text. --> - <string name="usb_storage_error_message">There is a problem using your SD card for USB storage.</string> + <string name="usb_storage_error_message" product="default">There is a problem using your SD card for USB storage.</string> <!-- USB_STORAGE: When the user connects the phone to a computer via USB, we show a notification asking if he wants to share files across. This is the title --> <string name="usb_storage_notification_title">USB connected</string> <!-- See USB_STORAGE. This is the message. --> @@ -2102,8 +2109,10 @@ <!-- This is the label for the activity, and should never be visible to the user. --> <!-- See USB_STORAGE_STOP. USB_STORAGE_STOP_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to stop usb storage. This is the title. --> <string name="usb_storage_stop_title">USB storage in use</string> + <!-- See USB_STORAGE_STOP. This is the message. [CHAR LIMIT=NONE] --> + <string name="usb_storage_stop_message" product="nosdcard">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s shared storage from your computer.</string> <!-- See USB_STORAGE_STOP. This is the message. --> - <string name="usb_storage_stop_message">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s SD card from your computer.</string> + <string name="usb_storage_stop_message" product="default">Before turning off USB storage, make sure you have unmounted (\u201cejected\u201d) your Android\u2018s SD card from your computer.</string> <!-- See USB_STORAGE_STOP. This is the button text to stop usb storage. --> <string name="usb_storage_stop_button_mount">Turn off USB storage</string> <!-- See USB_STORAGE_STOP_DIALOG. If there was an error stopping, this is the text. --> @@ -2120,10 +2129,14 @@ <!-- External media format dialog strings --> <!-- This is the label for the activity, and should never be visible to the user. --> + <!-- See EXTMEDIA_FORMAT. EXTMEDIA_FORMAT_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to format the SD card. This is the title. [CHAR LIMIT=20] --> + <string name="extmedia_format_title" product="nosdcard">Format shared storage</string> <!-- See EXTMEDIA_FORMAT. EXTMEDIA_FORMAT_DIALOG: After the user selects the notification, a dialog is shown asking if he wants to format the SD card. This is the title. --> - <string name="extmedia_format_title">Format SD card</string> + <string name="extmedia_format_title" product="default">Format SD card</string> + <!-- See EXTMEDIA_FORMAT. This is the message. [CHAR LIMIT=NONE] --> + <string name="extmedia_format_message" product="nosdcard">Format shared storage, erasing all files stored there? Action cannot be reversed!</string> <!-- See EXTMEDIA_FORMAT. This is the message. --> - <string name="extmedia_format_message">Are you sure you want to format the SD card? All data on your card will be lost.</string> + <string name="extmedia_format_message" product="default">Are you sure you want to format the SD card? All data on your card will be lost.</string> <!-- See EXTMEDIA_FORMAT. This is the button text to format the sd card. --> <string name="extmedia_format_button_format">Format</string> @@ -2148,29 +2161,51 @@ <string name="candidates_style"><u>candidates</u></string> <!-- External media notification strings --> + <!-- Shown when external media is being checked [CHAR LIMIT=30] --> + <string name="ext_media_checking_notification_title" product="nosdcard">Preparing shared storage</string> <!-- Shown when external media is being checked --> - <string name="ext_media_checking_notification_title">Preparing SD card</string> + <string name="ext_media_checking_notification_title" product="default">Preparing SD card</string> <string name="ext_media_checking_notification_message">Checking for errors.</string> + <!-- Shown when external media is blank (or unsupported filesystem) [CHAR LIMIT=30] --> + <string name="ext_media_nofs_notification_title" product="nosdcard">Blank shared storage</string> <!-- Shown when external media is blank (or unsupported filesystem) --> - <string name="ext_media_nofs_notification_title">Blank SD card</string> - <string name="ext_media_nofs_notification_message">SD card blank or has unsupported filesystem.</string> + <string name="ext_media_nofs_notification_title" product="default">Blank SD card</string> + <!-- Shown when shared storage cannot be read. [CHAR LIMIT=NONE] --> + <string name="ext_media_nofs_notification_message" product="nosdcard">Shared storage blank or has unsupported filesystem.</string> + <string name="ext_media_nofs_notification_message" product="default">SD card blank or has unsupported filesystem.</string> + <!-- Shown when external media is unmountable (corrupt)) [CHAR LIMIT=30] --> + <string name="ext_media_unmountable_notification_title" product="nosdcard">Damaged shared storage</string> <!-- Shown when external media is unmountable (corrupt)) --> - <string name="ext_media_unmountable_notification_title">Damaged SD card</string> - <string name="ext_media_unmountable_notification_message">SD card damaged. You may have to reformat it.</string> + <string name="ext_media_unmountable_notification_title" product="default">Damaged SD card</string> + <!-- Shown when shared storage cannot be read. [CHAR LIMIT=NONE] --> + <string name="ext_media_unmountable_notification_message" product="nosdcard">Shared storage damaged. You may have to reformat it.</string> + <string name="ext_media_unmountable_notification_message" product="default">SD card damaged. You may have to reformat it.</string> + <!-- Shown when external media is unsafely removed [CHAR LIMIT=30] --> + <string name="ext_media_badremoval_notification_title" product="nosdcard">Shared storage unexpectedly removed</string> <!-- Shown when external media is unsafely removed --> - <string name="ext_media_badremoval_notification_title">SD card unexpectedly removed</string> - <string name="ext_media_badremoval_notification_message">Unmount SD card before removing to avoid data loss.</string> + <string name="ext_media_badremoval_notification_title" product="default">SD card unexpectedly removed</string> + <!-- Shown when external media is unsafely removed. [CHAR LIMIT=NONE] --> + <string name="ext_media_badremoval_notification_message" product="nosdcard">Unmount shared storage before removing to avoid data loss.</string> + <string name="ext_media_badremoval_notification_message" product="default">Unmount SD card before removing to avoid data loss.</string> + <!-- Shown when external media has been safely removed [CHAR LIMIT=30] --> + <string name="ext_media_safe_unmount_notification_title" product="nosdcard">Shared storage safe to remove</string> <!-- Shown when external media has been safely removed --> - <string name="ext_media_safe_unmount_notification_title">SD card safe to remove</string> - <string name="ext_media_safe_unmount_notification_message">You can safely remove SD card.</string> + <string name="ext_media_safe_unmount_notification_title" product="default">SD card safe to remove</string> + <!-- Shown when external media has been safely removed. [CHAR LIMIT=NONE] --> + <string name="ext_media_safe_unmount_notification_message" product="nosdcard">You can safely remove shared storage.</string> + <string name="ext_media_safe_unmount_notification_message" product="default">You can safely remove SD card.</string> + <!-- Shown when external media is missing [CHAR LIMIT=30] --> + <string name="ext_media_nomedia_notification_title" product="nosdcard">Removed shared storage</string> <!-- Shown when external media is missing --> - <string name="ext_media_nomedia_notification_title">Removed SD card</string> - <string name="ext_media_nomedia_notification_message">SD card removed. Insert a new one.</string> + <string name="ext_media_nomedia_notification_title" product="default">Removed SD card</string> + <!-- Shown when external media is missing. [CHAR LIMIT=NONE] --> + <string name="ext_media_nomedia_notification_message" product="nosdcard">Shared storage removed. Insert new media.</string> + <string name="ext_media_nomedia_notification_message" product="default">SD card removed. Insert a new one.</string> <!-- Shown in LauncherActivity when the requested target Intent didn't return any matching Activities, leaving the list empty. --> <string name="activity_list_empty">No matching activities found</string> diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png Binary files differindex c299e12..ae90cc8 100755 --- a/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_data_fully_in_e.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png Binary files differindex 157491e..a0e59cf 100644 --- a/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_no_sim.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png Binary files differindex 3e317dd..2f66b1d 100644 --- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_0_fully.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png Binary files differindex aea18ed..1626895 100644 --- a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1.png diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png Binary files differindex 1a25a2c..3c2e2b9 100755..100644 --- a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png +++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_1_fully.png diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png Binary files differindex 2134d49..bb41db0 100644 --- a/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png +++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_no_sim.png diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java index 70d4d6a..d4491d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java @@ -20,6 +20,8 @@ import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.Slog; +import android.view.View; import android.widget.ImageView; import android.widget.RemoteViews.RemoteView; @@ -43,7 +45,7 @@ public class AnimatedImageView extends ImageView { } if (drawable instanceof AnimationDrawable) { mAnim = (AnimationDrawable)drawable; - if (mAttached) { + if (isShown()) { mAnim.start(); } } else { @@ -67,9 +69,6 @@ public class AnimatedImageView extends ImageView { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); - if (mAnim != null) { - mAnim.start(); - } mAttached = true; } @@ -81,5 +80,17 @@ public class AnimatedImageView extends ImageView { } mAttached = false; } + + @Override + protected void onVisibilityChanged(View changedView, int vis) { + super.onVisibilityChanged(changedView, vis); + if (mAnim != null) { + if (isShown()) { + mAnim.start(); + } else { + mAnim.stop(); + } + } + } } diff --git a/packages/TtsService/Android.mk b/packages/TtsService/Android.mk index 75b26a2..a1a3b9f 100644 --- a/packages/TtsService/Android.mk +++ b/packages/TtsService/Android.mk @@ -8,7 +8,7 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) \ LOCAL_PACKAGE_NAME := TtsService LOCAL_CERTIFICATE := platform -LOCAL_PROGUARD_FLAGS := -include $(LOCAL_PATH)/proguard.flags +LOCAL_PROGUARD_FLAG_FILES := proguard.flags include $(BUILD_PACKAGE) diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java index 5d32b74..71105f1 100644 --- a/services/java/com/android/server/PowerManagerService.java +++ b/services/java/com/android/server/PowerManagerService.java @@ -2105,6 +2105,7 @@ class PowerManagerService extends IPowerManager.Stub } public void userActivity(long time, boolean noChangeLights) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); userActivity(time, -1, noChangeLights, OTHER_EVENT, false); } @@ -2128,7 +2129,6 @@ class PowerManagerService extends IPowerManager.Stub private void userActivity(long time, long timeoutOverride, boolean noChangeLights, int eventType, boolean force) { - //mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); if (((mPokey & POKE_LOCK_IGNORE_CHEEK_EVENTS) != 0) && (eventType == CHEEK_EVENT || eventType == TOUCH_EVENT)) { diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java index 798a5a5..25ca559 100644 --- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java +++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java @@ -24,9 +24,10 @@ import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.provider.ContactsContract.CommonDataKinds.SipAddress; +import android.provider.ContactsContract.Data; import android.provider.ContactsContract.PhoneLookup; import android.telephony.PhoneNumberUtils; -import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; @@ -36,7 +37,7 @@ import android.util.Log; public class CallerInfoAsyncQuery { - private static final boolean DBG = false; + private static final boolean DBG = true; // STOPSHIP: disable debugging before ship private static final String LOG_TAG = "CallerInfoAsyncQuery"; private static final int EVENT_NEW_QUERY = 1; @@ -189,7 +190,7 @@ public class CallerInfoAsyncQuery { */ @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - if (DBG) log("query complete for token: " + token); + if (DBG) log("##### onQueryComplete() ##### query complete for token: " + token); //get the cookie and notify the listener. CookieWrapper cw = (CookieWrapper) cookie; @@ -227,6 +228,8 @@ public class CallerInfoAsyncQuery { mCallerInfo = new CallerInfo().markAsVoiceMail(); } else { mCallerInfo = CallerInfo.getCallerInfo(mQueryContext, mQueryUri, cursor); + if (DBG) log("==> Got mCallerInfo: " + mCallerInfo); + // Use the number entered by the user for display. if (!TextUtils.isEmpty(cw.number)) { mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number); @@ -238,7 +241,7 @@ public class CallerInfoAsyncQuery { //notify that we can clean up the queue after this. CookieWrapper endMarker = new CookieWrapper(); endMarker.event = EVENT_END_OF_QUEUE; - startQuery (token, endMarker, null, null, null, null, null); + startQuery(token, endMarker, null, null, null, null, null); } //notify the listener that the query is complete. @@ -274,24 +277,82 @@ public class CallerInfoAsyncQuery { cw.cookie = cookie; cw.event = EVENT_NEW_QUERY; - c.mHandler.startQuery (token, cw, contactRef, null, null, null, null); + c.mHandler.startQuery(token, cw, contactRef, null, null, null, null); return c; } /** - * Factory method to start query with a number + * Factory method to start the query based on a number. + * + * Note: if the number contains an "@" character we treat it + * as a SIP address, and look it up directly in the Data table + * rather than using the PhoneLookup table. + * TODO: But eventually we should expose two separate methods, one for + * numbers and one for SIP addresses, and then have + * PhoneUtils.startGetCallerInfo() decide which one to call based on + * the phone type of the incoming connection. */ public static CallerInfoAsyncQuery startQuery(int token, Context context, String number, OnQueryCompleteListener listener, Object cookie) { - //construct the URI object and start Query. - Uri contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + if (DBG) { + log("##### CallerInfoAsyncQuery startQuery()... #####"); + log("- number: " + number); + log("- cookie: " + cookie); + } + + // Construct the URI object and query params, and start the query. + + Uri contactRef; + String selection; + String[] selectionArgs; + + if (PhoneNumberUtils.isUriNumber(number)) { + // "number" is really a SIP address. + if (DBG) log(" - Treating number as a SIP address: " + number); + + // We look up SIP addresses directly in the Data table: + contactRef = Data.CONTENT_URI; + + // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent. + // + // Also note we use "upper(data1)" in the WHERE clause, and + // uppercase the incoming SIP address, in order to do a + // case-insensitive match. + // + // TODO: need to confirm that the use of upper() doesn't + // prevent us from using the index! (Linear scan of the whole + // contacts DB can be very slow.) + // + // TODO: May also need to normalize by adding "sip:" as a + // prefix, if we start storing SIP addresses that way in the + // database. + + selection = "upper(" + Data.DATA1 + ")=?" + + " AND " + + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'"; + selectionArgs = new String[] { number.toUpperCase() }; + + } else { + // "number" is a regular phone number. Use the PhoneLookup table: + contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + selection = null; + selectionArgs = null; + } + + if (DBG) { + log("==> contactRef: " + contactRef); + log("==> selection: " + selection); + if (selectionArgs != null) { + for (int i = 0; i < selectionArgs.length; i++) { + log("==> selectionArgs[" + i + "]: " + selectionArgs[i]); + } + } + } CallerInfoAsyncQuery c = new CallerInfoAsyncQuery(); c.allocate(context, contactRef); - if (DBG) log("starting query for number: " + number + " handler: " + c.toString()); - //create cookieWrapper, start query CookieWrapper cw = new CookieWrapper(); cw.listener = listener; @@ -307,10 +368,15 @@ public class CallerInfoAsyncQuery { cw.event = EVENT_NEW_QUERY; } - c.mHandler.startQuery (token, cw, contactRef, null, null, null, null); - + c.mHandler.startQuery(token, + cw, // cookie + contactRef, // uri + null, // projection + selection, // selection + selectionArgs, // selectionArgs + null); // orderBy return c; - } + } /** * Method to add listeners to a currently running query @@ -326,7 +392,7 @@ public class CallerInfoAsyncQuery { cw.cookie = cookie; cw.event = EVENT_ADD_LISTENER; - mHandler.startQuery (token, cw, null, null, null, null, null); + mHandler.startQuery(token, cw, null, null, null, null, null); } /** diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java index bceceda..af3e0886 100755 --- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java +++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java @@ -126,14 +126,27 @@ public class SipPhone extends SipPhoneBase { return false; } - SipAudioCall sipAudioCall = (SipAudioCall) incomingCall; - Log.v(LOG_TAG, " ++++++ taking call from: " - + sipAudioCall.getPeerProfile().getUriString()); - String localUri = sipAudioCall.getLocalProfile().getUriString(); - if (localUri.equals(mProfile.getUriString())) { - boolean makeCallWait = foregroundCall.getState().isAlive(); - ringingCall.initIncomingCall(sipAudioCall, makeCallWait); - return true; + try { + SipAudioCall sipAudioCall = (SipAudioCall) incomingCall; + Log.d(LOG_TAG, "+++ taking call from: " + + sipAudioCall.getPeerProfile().getUriString()); + String localUri = sipAudioCall.getLocalProfile().getUriString(); + if (localUri.equals(mProfile.getUriString())) { + boolean makeCallWait = foregroundCall.getState().isAlive(); + ringingCall.initIncomingCall(sipAudioCall, makeCallWait); + if (sipAudioCall.getState() + != SipSession.State.INCOMING_CALL) { + // Peer cancelled the call! + Log.d(LOG_TAG, " call cancelled !!"); + ringingCall.reset(); + } + return true; + } + } catch (Exception e) { + // Peer may cancel the call at any time during the time we hook + // up ringingCall with sipAudioCall. Clean up ringingCall when + // that happens. + ringingCall.reset(); } return false; } @@ -358,6 +371,11 @@ public class SipPhone extends SipPhoneBase { } private class SipCall extends SipCallBase { + void reset() { + connections.clear(); + setState(Call.State.IDLE); + } + void switchWith(SipCall that) { synchronized (SipPhone.class) { SipCall tmp = new SipCall(); @@ -444,6 +462,7 @@ public class SipPhone extends SipPhoneBase { if (state.isAlive()) { Log.d(LOG_TAG, "hang up call: " + getState() + ": " + this + " on phone " + getPhone()); + setState(State.DISCONNECTING); CallStateException excp = null; for (Connection c : connections) { try { @@ -453,7 +472,6 @@ public class SipPhone extends SipPhoneBase { } } if (excp != null) throw excp; - setState(State.DISCONNECTING); } else { Log.d(LOG_TAG, "hang up dead call: " + getState() + ": " + this + " on phone " + getPhone()); @@ -630,13 +648,20 @@ public class SipPhone extends SipPhoneBase { } synchronized (SipPhone.class) { setState(Call.State.DISCONNECTED); - mSipAudioCall.close(); - mOwner.onConnectionEnded(SipConnection.this); - Log.v(LOG_TAG, "-------- connection ended: " - + mPeer.getUriString() + ": " - + mSipAudioCall.getState() + ", cause: " - + getDisconnectCause() + ", on phone " + SipAudioCall sipAudioCall = mSipAudioCall; + mSipAudioCall = null; + String sessionState = (sipAudioCall == null) + ? "" + : (sipAudioCall.getState() + ", "); + Log.v(LOG_TAG, "--- connection ended: " + + mPeer.getUriString() + ": " + sessionState + + "cause: " + getDisconnectCause() + ", on phone " + getPhone()); + if (sipAudioCall != null) { + sipAudioCall.setListener(null); + sipAudioCall.close(); + } + mOwner.onConnectionEnded(SipConnection.this); } } @@ -790,14 +815,17 @@ public class SipPhone extends SipPhoneBase { synchronized (SipPhone.class) { Log.v(LOG_TAG, "hangup conn: " + mPeer.getUriString() + ": " + mState + ": on phone " + getPhone().getPhoneName()); + if (!mState.isAlive()) return; try { - if (mState.isAlive()) { - if (mSipAudioCall != null) mSipAudioCall.endCall(); - setState(Call.State.DISCONNECTING); - setDisconnectCause(DisconnectCause.LOCAL); + SipAudioCall sipAudioCall = mSipAudioCall; + if (sipAudioCall != null) { + sipAudioCall.setListener(null); + sipAudioCall.endCall(); } } catch (SipException e) { throw new CallStateException("hangup(): " + e); + } finally { + mAdapter.onCallEnded(DisconnectCause.LOCAL); } } } diff --git a/voip/java/android/net/rtp/AudioCodec.java b/voip/java/android/net/rtp/AudioCodec.java index dfa6841..3877aeb 100644 --- a/voip/java/android/net/rtp/AudioCodec.java +++ b/voip/java/android/net/rtp/AudioCodec.java @@ -80,8 +80,7 @@ public class AudioCodec { */ public static final AudioCodec AMR = new AudioCodec(97, "AMR/8000", null); - // TODO: add rest of the codecs when the native part is done. - private static final AudioCodec[] sCodecs = {GSM, PCMU, PCMA}; + private static final AudioCodec[] sCodecs = {GSM_EFR, AMR, GSM, PCMU, PCMA}; private AudioCodec(int type, String rtpmap, String fmtp) { this.type = type; diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java index 59631c1..52f5716 100644 --- a/voip/java/android/net/sip/SipManager.java +++ b/voip/java/android/net/sip/SipManager.java @@ -520,7 +520,9 @@ public class SipManager { private String getUri(ISipSession session) { try { - return session.getLocalProfile().getUriString(); + return ((session == null) + ? "no session" + : session.getLocalProfile().getUriString()); } catch (RemoteException e) { throw new RuntimeException(e); } diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java index 0ff5586..130fe9f 100644 --- a/voip/java/com/android/server/sip/SipService.java +++ b/voip/java/com/android/server/sip/SipService.java @@ -510,31 +510,43 @@ public final class SipService extends ISipService.Stub { } } + // KeepAliveProcess is controlled by AutoRegistrationProcess. + // All methods will be invoked in sync with SipService.this except realRun() 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; public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { mSession = session; } public void start() { + if (mRunning) return; + mRunning = true; mTimer.set(INTERVAL * 1000, this); } + // timeout handler public void run() { + if (!mRunning) return; + final SipSessionGroup.SipSessionImpl session = mSession; + // delegate to mExecutor getExecutor().addTask(new Runnable() { public void run() { - realRun(); + realRun(session); } }); } - private void realRun() { + // real timeout handler + private void realRun(SipSessionGroup.SipSessionImpl session) { synchronized (SipService.this) { - SipSessionGroup.SipSessionImpl session = mSession.duplicate(); + if (notCurrentSession(session)) return; + + session = session.duplicate(); if (DEBUG) Log.d(TAG, "~~~ keepalive"); mTimer.cancel(this); session.sendKeepAlive(); @@ -547,8 +559,14 @@ public final class SipService extends ISipService.Stub { } public void stop() { + mRunning = false; + mSession = null; mTimer.cancel(this); } + + private boolean notCurrentSession(ISipSession session) { + return (session != mSession) || !mRunning; + } } private class AutoRegistrationProcess extends SipSessionAdapter @@ -561,13 +579,15 @@ public final class SipService extends ISipService.Stub { private long mExpiryTime; private int mErrorCode; private String mErrorMessage; + private boolean mRunning = false; private String getAction() { return toString(); } public void start(SipSessionGroup group) { - if (mSession == null) { + if (!mRunning) { + mRunning = true; mBackoff = 1; mSession = (SipSessionGroup.SipSessionImpl) group.createSession(this); @@ -584,35 +604,24 @@ public final class SipService extends ISipService.Stub { } public void stop() { - stop(false); - } - - private void stopButKeepStates() { - stop(true); - } - - private void stop(boolean keepStates) { - if (mSession == null) return; + if (!mRunning) return; + mRunning = false; + mSession.setListener(null); if (mConnected && mRegistered) mSession.unregister(); + mTimer.cancel(this); if (mKeepAliveProcess != null) { mKeepAliveProcess.stop(); mKeepAliveProcess = null; } - if (!keepStates) { - mSession = null; - mRegistered = false; - } - } - private boolean isStopped() { - return (mSession == null); + mRegistered = false; + setListener(mProxy.getListener()); } public void setListener(ISipSessionListener listener) { synchronized (SipService.this) { mProxy.setListener(listener); - if (mSession == null) return; try { int state = (mSession == null) @@ -632,6 +641,18 @@ public final class SipService extends ISipService.Stub { mProxy.onRegistrationFailed(mSession, mErrorCode, mErrorMessage); } + } else if (!mConnected) { + mProxy.onRegistrationFailed(mSession, + SipErrorCode.DATA_CONNECTION_LOST, + "no data connection"); + } else if (!mRunning) { + mProxy.onRegistrationFailed(mSession, + SipErrorCode.CLIENT_ERROR, + "registration not running"); + } else { + mProxy.onRegistrationFailed(mSession, + SipErrorCode.IN_PROGRESS, + String.valueOf(state)); } } catch (Throwable t) { Log.w(TAG, "setListener(): " + t); @@ -643,21 +664,29 @@ public final class SipService extends ISipService.Stub { return mRegistered; } + // timeout handler public void run() { - // delegate to mExecutor - getExecutor().addTask(new Runnable() { - public void run() { - realRun(); - } - }); + synchronized (SipService.this) { + if (!mRunning) return; + final SipSessionGroup.SipSessionImpl session = mSession; + + // delegate to mExecutor + getExecutor().addTask(new Runnable() { + public void run() { + realRun(session); + } + }); + } } - private void realRun() { - mErrorCode = SipErrorCode.NO_ERROR; - mErrorMessage = null; - if (DEBUG) Log.d(TAG, "~~~ registering"); + // real timeout handler + private void realRun(SipSessionGroup.SipSessionImpl session) { synchronized (SipService.this) { - if (mConnected && !isStopped()) mSession.register(EXPIRY_TIME); + if (notCurrentSession(session)) return; + mErrorCode = SipErrorCode.NO_ERROR; + mErrorMessage = null; + if (DEBUG) Log.d(TAG, "~~~ registering"); + if (mConnected) session.register(EXPIRY_TIME); } } @@ -697,22 +726,29 @@ public final class SipService extends ISipService.Stub { public void onRegistering(ISipSession session) { if (DEBUG) Log.d(TAG, "onRegistering(): " + session); synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; + if (notCurrentSession(session)) return; + mRegistered = false; mProxy.onRegistering(session); } } + private boolean notCurrentSession(ISipSession session) { + if (session != mSession) { + ((SipSessionGroup.SipSessionImpl) session).setListener(null); + return true; + } + return !mRunning; + } + @Override public void onRegistrationDone(ISipSession session, int duration) { if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session); synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; + if (notCurrentSession(session)) return; mProxy.onRegistrationDone(session, duration); - if (isStopped()) return; - if (duration > 0) { mSession.clearReRegisterRequired(); mExpiryTime = SystemClock.elapsedRealtime() @@ -751,17 +787,18 @@ public final class SipService extends ISipService.Stub { if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": " + SipErrorCode.toString(errorCode) + ": " + message); synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; - mErrorCode = errorCode; - mErrorMessage = message; - mProxy.onRegistrationFailed(session, errorCode, message); + if (notCurrentSession(session)) return; if (errorCode == SipErrorCode.INVALID_CREDENTIALS) { if (DEBUG) Log.d(TAG, " pause auto-registration"); - stopButKeepStates(); - } else if (!isStopped()) { + stop(); + } else { onError(); } + + mErrorCode = errorCode; + mErrorMessage = message; + mProxy.onRegistrationFailed(session, errorCode, message); } } @@ -769,14 +806,11 @@ public final class SipService extends ISipService.Stub { public void onRegistrationTimeout(ISipSession session) { if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session); synchronized (SipService.this) { - if (!isStopped() && (session != mSession)) return; + if (notCurrentSession(session)) return; + mErrorCode = SipErrorCode.TIME_OUT; mProxy.onRegistrationTimeout(session); - - if (!isStopped()) { - mRegistered = false; - onError(); - } + onError(); } } @@ -883,6 +917,7 @@ public final class SipService extends ISipService.Stub { mConnected = connected; } + // timeout handler @Override public void run() { // delegate to mExecutor diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java index 4321d7b..c68fa1b 100644 --- a/voip/java/com/android/server/sip/SipSessionGroup.java +++ b/voip/java/com/android/server/sip/SipSessionGroup.java @@ -334,12 +334,12 @@ class SipSessionGroup implements SipListener { if (isRequestEvent(Request.INVITE, evt)) { RequestEvent event = (RequestEvent) evt; SipSessionImpl newSession = new SipSessionImpl(mProxy); + newSession.mState = SipSession.State.INCOMING_CALL; newSession.mServerTransaction = mSipHelper.sendRinging(event, generateTag()); newSession.mDialog = newSession.mServerTransaction.getDialog(); newSession.mInviteReceived = event; newSession.mPeerProfile = createPeerProfile(event.getRequest()); - newSession.mState = SipSession.State.INCOMING_CALL; newSession.mPeerSessionDescription = extractContent(event.getRequest()); addSipSession(newSession); @@ -708,7 +708,6 @@ class SipSessionGroup implements SipListener { case SipSession.State.PINGING: reset(); mReRegisterFlag = true; - mState = SipSession.State.READY_TO_CALL; break; default: @@ -877,6 +876,7 @@ class SipSessionGroup implements SipListener { private boolean readyForCall(EventObject evt) throws SipException { // expect MakeCallCommand, RegisterCommand, DEREGISTER if (evt instanceof MakeCallCommand) { + mState = SipSession.State.OUTGOING_CALL; MakeCallCommand cmd = (MakeCallCommand) evt; mPeerProfile = cmd.getPeerProfile(); mClientTransaction = mSipHelper.sendInvite(mLocalProfile, @@ -884,25 +884,24 @@ class SipSessionGroup implements SipListener { generateTag()); mDialog = mClientTransaction.getDialog(); addSipSession(this); - mState = SipSession.State.OUTGOING_CALL; mProxy.onCalling(this); startSessionTimer(cmd.getTimeout()); return true; } else if (evt instanceof RegisterCommand) { + mState = SipSession.State.REGISTERING; int duration = ((RegisterCommand) evt).getDuration(); mClientTransaction = mSipHelper.sendRegister(mLocalProfile, generateTag(), duration); mDialog = mClientTransaction.getDialog(); addSipSession(this); - mState = SipSession.State.REGISTERING; mProxy.onRegistering(this); return true; } else if (DEREGISTER == evt) { + mState = SipSession.State.DEREGISTERING; mClientTransaction = mSipHelper.sendRegister(mLocalProfile, generateTag(), 0); mDialog = mClientTransaction.getDialog(); addSipSession(this); - mState = SipSession.State.DEREGISTERING; mProxy.onRegistering(this); return true; } @@ -913,11 +912,11 @@ class SipSessionGroup implements SipListener { // expect MakeCallCommand(answering) , END_CALL cmd , Cancel if (evt instanceof MakeCallCommand) { // answer call + mState = SipSession.State.INCOMING_CALL_ANSWERING; mServerTransaction = mSipHelper.sendInviteOk(mInviteReceived, mLocalProfile, ((MakeCallCommand) evt).getSessionDescription(), mServerTransaction); - mState = SipSession.State.INCOMING_CALL_ANSWERING; startSessionTimer(((MakeCallCommand) evt).getTimeout()); return true; } else if (END_CALL == evt) { @@ -1009,8 +1008,8 @@ class SipSessionGroup implements SipListener { // RFC says that UA should not send out cancel when no // response comes back yet. We are cheating for not checking // response. - mSipHelper.sendCancel(mClientTransaction); mState = SipSession.State.OUTGOING_CALL_CANCELING; + mSipHelper.sendCancel(mClientTransaction); startSessionTimer(CANCEL_CALL_TIMER); return true; } @@ -1065,8 +1064,8 @@ class SipSessionGroup implements SipListener { return true; } else if (isRequestEvent(Request.INVITE, evt)) { // got Re-INVITE - RequestEvent event = mInviteReceived = (RequestEvent) evt; mState = SipSession.State.INCOMING_CALL; + RequestEvent event = mInviteReceived = (RequestEvent) evt; mPeerSessionDescription = extractContent(event.getRequest()); mServerTransaction = null; mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription); @@ -1077,9 +1076,9 @@ class SipSessionGroup implements SipListener { return true; } else if (evt instanceof MakeCallCommand) { // to change call + mState = SipSession.State.OUTGOING_CALL; mClientTransaction = mSipHelper.sendReinvite(mDialog, ((MakeCallCommand) evt).getSessionDescription()); - mState = SipSession.State.OUTGOING_CALL; startSessionTimer(((MakeCallCommand) evt).getTimeout()); return true; } diff --git a/voip/jni/rtp/AmrCodec.cpp b/voip/jni/rtp/AmrCodec.cpp new file mode 100644 index 0000000..f3ecac2 --- /dev/null +++ b/voip/jni/rtp/AmrCodec.cpp @@ -0,0 +1,268 @@ +/* + * Copyrightm (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <string.h> + +#include "AudioCodec.h" + +#include "gsmamr_dec.h" +#include "gsmamr_enc.h" + +namespace { + +const int gFrameBits[8] = {95, 103, 118, 134, 148, 159, 204, 244}; + +//------------------------------------------------------------------------------ + +// See RFC 4867 for the encoding details. + +class AmrCodec : public AudioCodec +{ +public: + AmrCodec() { + if (AMREncodeInit(&mEncoder, &mSidSync, false)) { + mEncoder = NULL; + } + if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) { + mDecoder = NULL; + } + } + + ~AmrCodec() { + if (mEncoder) { + AMREncodeExit(&mEncoder, &mSidSync); + } + if (mDecoder) { + GSMDecodeFrameExit(&mDecoder); + } + } + + int set(int sampleRate, const char *fmtp); + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); + +private: + void *mEncoder; + void *mSidSync; + void *mDecoder; + + int mMode; + int mModeSet; + bool mOctetAligned; +}; + +int AmrCodec::set(int sampleRate, const char *fmtp) +{ + // These parameters are not supported. + if (strcasestr(fmtp, "crc=1") || strcasestr(fmtp, "robust-sorting=1") || + strcasestr(fmtp, "interleaving=")) { + return -1; + } + + // Handle mode-set and octet-align. + char *modes = strcasestr(fmtp, "mode-set="); + if (modes) { + mMode = 0; + mModeSet = 0; + for (char c = *modes; c && c != ' '; c = *++modes) { + if (c >= '0' && c <= '7') { + int mode = c - '0'; + if (mode > mMode) { + mMode = mode; + } + mModeSet |= 1 << mode; + } + } + } else { + mMode = 7; + mModeSet = 0xFF; + } + mOctetAligned = (strcasestr(fmtp, "octet-align=1") != NULL); + + // TODO: handle mode-change-*. + + return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1; +} + +int AmrCodec::encode(void *payload, int16_t *samples) +{ + unsigned char *bytes = (unsigned char *)payload; + Frame_Type_3GPP type; + + int length = AMREncode(mEncoder, mSidSync, (Mode)mMode, + samples, bytes + 1, &type, AMR_TX_WMF); + + if (type != mMode || length != (8 + gFrameBits[mMode] + 7) >> 3) { + return -1; + } + + if (mOctetAligned) { + bytes[0] = 0xF0; + bytes[1] = (mMode << 3) | 0x04; + ++length; + } else { + // CMR = 15 (4-bit), F = 0 (1-bit), FT = mMode (4-bit), Q = 1 (1-bit). + bytes[0] = 0xFF; + bytes[1] = 0xC0 | (mMode << 1) | 1; + + // Shift left 6 bits and update the length. + bytes[length + 1] = 0; + for (int i = 0; i <= length; ++i) { + bytes[i] = (bytes[i] << 6) | (bytes[i + 1] >> 2); + } + length = (10 + gFrameBits[mMode] + 7) >> 3; + } + return length; +} + +int AmrCodec::decode(int16_t *samples, void *payload, int length) +{ + unsigned char *bytes = (unsigned char *)payload; + Frame_Type_3GPP type; + if (length < 2) { + return -1; + } + int request = bytes[0] >> 4; + + if (mOctetAligned) { + if ((bytes[1] & 0xC4) != 0x04) { + return -1; + } + type = (Frame_Type_3GPP)(bytes[1] >> 3); + if (length != (16 + gFrameBits[type] + 7) >> 3) { + return -1; + } + length -= 2; + bytes += 2; + } else { + if ((bytes[0] & 0x0C) || !(bytes[1] & 0x40)) { + return -1; + } + type = (Frame_Type_3GPP)((bytes[0] << 1 | bytes[1] >> 7) & 0x07); + if (length != (10 + gFrameBits[type] + 7) >> 3) { + return -1; + } + + // Shift left 2 bits and update the length. + --length; + for (int i = 1; i < length; ++i) { + bytes[i] = (bytes[i] << 2) | (bytes[i + 1] >> 6); + } + bytes[length] <<= 2; + length = (gFrameBits[type] + 7) >> 3; + ++bytes; + } + + if (AMRDecode(mDecoder, type, bytes, samples, MIME_IETF) != length) { + return -1; + } + + // Handle CMR + if (request < 8 && request != mMode) { + for (int i = request; i >= 0; --i) { + if (mModeSet & (1 << i)) { + mMode = request; + break; + } + } + } + + return 160; +} + +//------------------------------------------------------------------------------ + +// See RFC 3551 for the encoding details. + +class GsmEfrCodec : public AudioCodec +{ +public: + GsmEfrCodec() { + if (AMREncodeInit(&mEncoder, &mSidSync, false)) { + mEncoder = NULL; + } + if (GSMInitDecode(&mDecoder, (Word8 *)"RTP")) { + mDecoder = NULL; + } + } + + ~GsmEfrCodec() { + if (mEncoder) { + AMREncodeExit(&mEncoder, &mSidSync); + } + if (mDecoder) { + GSMDecodeFrameExit(&mDecoder); + } + } + + int set(int sampleRate, const char *fmtp) { + return (sampleRate == 8000 && mEncoder && mDecoder) ? 160 : -1; + } + + int encode(void *payload, int16_t *samples); + int decode(int16_t *samples, void *payload, int length); + +private: + void *mEncoder; + void *mSidSync; + void *mDecoder; +}; + +int GsmEfrCodec::encode(void *payload, int16_t *samples) +{ + unsigned char *bytes = (unsigned char *)payload; + Frame_Type_3GPP type; + + int length = AMREncode(mEncoder, mSidSync, MR122, + samples, bytes, &type, AMR_TX_WMF); + + if (type == AMR_122 && length == 32) { + bytes[0] = 0xC0 | (bytes[1] >> 4); + for (int i = 1; i < 31; ++i) { + bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4); + } + return 31; + } + return -1; +} + +int GsmEfrCodec::decode(int16_t *samples, void *payload, int length) +{ + unsigned char *bytes = (unsigned char *)payload; + if (length == 31 && (bytes[0] >> 4) == 0x0C) { + for (int i = 0; i < 30; ++i) { + bytes[i] = (bytes[i] << 4) | (bytes[i + 1] >> 4); + } + bytes[30] <<= 4; + + if (AMRDecode(mDecoder, AMR_122, bytes, samples, MIME_IETF) == 31) { + return 160; + } + } + return -1; +} + +} // namespace + +AudioCodec *newAmrCodec() +{ + return new AmrCodec; +} + +AudioCodec *newGsmEfrCodec() +{ + return new GsmEfrCodec; +} diff --git a/voip/jni/rtp/Android.mk b/voip/jni/rtp/Android.mk index 29683bd..5909c0d 100644 --- a/voip/jni/rtp/Android.mk +++ b/voip/jni/rtp/Android.mk @@ -27,6 +27,7 @@ LOCAL_SRC_FILES := \ rtp_jni.cpp LOCAL_SRC_FILES += \ + AmrCodec.cpp \ G711Codec.cpp \ GsmCodec.cpp @@ -34,13 +35,20 @@ LOCAL_SHARED_LIBRARIES := \ libnativehelper \ libcutils \ libutils \ - libmedia + libmedia \ + libstagefright LOCAL_STATIC_LIBRARIES := libgsm LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ - external/libgsm/inc + external/libgsm/inc \ + frameworks/base/media/libstagefright/codecs/amrnb/common/include \ + frameworks/base/media/libstagefright/codecs/amrnb/common/ \ + frameworks/base/media/libstagefright/codecs/amrnb/enc/include \ + frameworks/base/media/libstagefright/codecs/amrnb/enc/src \ + frameworks/base/media/libstagefright/codecs/amrnb/dec/include \ + frameworks/base/media/libstagefright/codecs/amrnb/dec/src LOCAL_CFLAGS += -fvisibility=hidden diff --git a/voip/jni/rtp/AudioCodec.cpp b/voip/jni/rtp/AudioCodec.cpp index fc33ef2..2267ea0 100644 --- a/voip/jni/rtp/AudioCodec.cpp +++ b/voip/jni/rtp/AudioCodec.cpp @@ -21,6 +21,8 @@ extern AudioCodec *newAlawCodec(); extern AudioCodec *newUlawCodec(); extern AudioCodec *newGsmCodec(); +extern AudioCodec *newAmrCodec(); +extern AudioCodec *newGsmEfrCodec(); struct AudioCodecType { const char *name; @@ -29,6 +31,8 @@ struct AudioCodecType { {"PCMA", newAlawCodec}, {"PCMU", newUlawCodec}, {"GSM", newGsmCodec}, + {"AMR", newAmrCodec}, + {"GSM-EFR", newGsmEfrCodec}, {NULL, NULL}, }; diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp index 72c882b..f09edb6 100644 --- a/voip/jni/rtp/AudioGroup.cpp +++ b/voip/jni/rtp/AudioGroup.cpp @@ -86,8 +86,6 @@ public: void decode(int tick); private: - bool isNatAddress(struct sockaddr_storage *addr); - enum { NORMAL = 0, SEND_ONLY = 1, @@ -101,6 +99,7 @@ private: AudioCodec *mCodec; uint32_t mCodecMagic; uint32_t mDtmfMagic; + bool mFixRemote; int mTick; int mSampleRate; @@ -181,6 +180,20 @@ bool AudioStream::set(int mode, int socket, sockaddr_storage *remote, if (codec) { mRemote = *remote; mCodec = codec; + + // Here we should never get an private address, but some buggy proxy + // servers do give us one. To solve this, we replace the address when + // the first time we successfully decode an incoming packet. + mFixRemote = false; + if (remote->ss_family == AF_INET) { + unsigned char *address = + (unsigned char *)&((sockaddr_in *)remote)->sin_addr; + if (address[0] == 10 || + (address[0] == 172 && (address[1] >> 4) == 1) || + (address[0] == 192 && address[1] == 168)) { + mFixRemote = true; + } + } } LOGD("stream[%d] is configured as %s %dkHz %dms", mSocket, @@ -318,16 +331,6 @@ void AudioStream::encode(int tick, AudioStream *chain) sizeof(mRemote)); } -bool AudioStream::isNatAddress(struct sockaddr_storage *addr) { - if (addr->ss_family != AF_INET) return false; - struct sockaddr_in *s4 = (struct sockaddr_in *)addr; - unsigned char *d = (unsigned char *) &s4->sin_addr; - if ((d[0] == 10) - || ((d[0] == 172) && (d[1] & 0x10)) - || ((d[0] == 192) && (d[1] == 168))) return true; - return false; -} - void AudioStream::decode(int tick) { char c; @@ -375,21 +378,11 @@ void AudioStream::decode(int tick) MSG_TRUNC | MSG_DONTWAIT) >> 1; } else { __attribute__((aligned(4))) uint8_t buffer[2048]; - struct sockaddr_storage src_addr; - socklen_t addrlen; + sockaddr_storage remote; + socklen_t len = sizeof(remote); + length = recvfrom(mSocket, buffer, sizeof(buffer), - MSG_TRUNC|MSG_DONTWAIT, (sockaddr*)&src_addr, &addrlen); - - // The following if clause is for fixing the target address if - // proxy server did not replace the NAT address with its media - // port in SDP. Although it is proxy server's responsibility for - // replacing the connection address with correct one, we will change - // the target address as we detect the difference for now until we - // know the best way to get rid of this issue. - if ((memcmp((void*)&src_addr, (void*)&mRemote, addrlen) != 0) && - isNatAddress(&mRemote)) { - memcpy((void*)&mRemote, (void*)&src_addr, addrlen); - } + MSG_TRUNC | MSG_DONTWAIT, (sockaddr *)&remote, &len); // Do we need to check SSRC, sequence, and timestamp? They are not // reliable but at least they can be used to identify duplicates? @@ -409,8 +402,12 @@ void AudioStream::decode(int tick) if (length >= 0) { length = mCodec->decode(samples, &buffer[offset], length); } + if (length > 0 && mFixRemote) { + mRemote = remote; + mFixRemote = false; + } } - if (length != mSampleCount) { + if (length <= 0) { LOGD("stream[%d] decoder error", mSocket); return; } |