diff options
38 files changed, 2901 insertions, 792 deletions
diff --git a/api/current.xml b/api/current.xml index d1d57ad..3a31034 100644 --- a/api/current.xml +++ b/api/current.xml @@ -48699,6 +48699,16 @@ visibility="public" > </field> +<field name="inPurgeable" + type="boolean" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="inSampleSize" type="int" transient="false" diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java new file mode 100644 index 0000000..18d62e3 --- /dev/null +++ b/core/java/android/app/FullBackupAgent.java @@ -0,0 +1,58 @@ +package android.app; + +import android.backup.BackupDataOutput; +import android.backup.FileBackupHelper; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * Backs up an application's entire /data/data/<package>/... file system. This + * class is used by the desktop full backup mechanism and is not intended for direct + * use by applications. + * + * {@hide} + */ + +public class FullBackupAgent extends BackupAgent { + // !!! TODO: turn off debugging + private static final String TAG = "FullBackupAgent"; + private static final boolean DEBUG = true; + + @Override + public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) { + LinkedList<File> dirsToScan = new LinkedList<File>(); + ArrayList<String> allFiles = new ArrayList<String>(); + + // build the list of files in the app's /data/data tree + dirsToScan.add(getFilesDir()); + if (DEBUG) Log.v(TAG, "Backing up dir tree @ " + getFilesDir().getAbsolutePath() + " :"); + while (dirsToScan.size() > 0) { + File dir = dirsToScan.removeFirst(); + File[] contents = dir.listFiles(); + if (contents != null) { + for (File f : contents) { + if (f.isDirectory()) { + dirsToScan.add(f); + } else if (f.isFile()) { + if (DEBUG) Log.v(TAG, " " + f.getAbsolutePath()); + allFiles.add(f.getAbsolutePath()); + } + } + } + } + + // That's the file set; now back it all up + FileBackupHelper.performBackup(this, oldState, data, newState, + (String[]) allFiles.toArray()); + } + + @Override + public void onRestore(ParcelFileDescriptor data, ParcelFileDescriptor newState) { + } + +} diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 3675ec2..bcb2791 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -1174,7 +1174,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ protected void launchQuerySearch(int actionKey, String actionMsg) { String query = mSearchAutoComplete.getText().toString(); - Intent intent = createIntent(Intent.ACTION_SEARCH, null, query, null, + Intent intent = createIntent(Intent.ACTION_SEARCH, null, query, null, actionKey, actionMsg); launchIntent(intent); } @@ -1202,13 +1202,26 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS protected boolean launchSuggestion(int position, int actionKey, String actionMsg) { Cursor c = mSuggestionsAdapter.getCursor(); if ((c != null) && c.moveToPosition(position)) { + // let the cursor know which position was clicked + final Bundle clickResponse = new Bundle(1); + clickResponse.putInt(SearchManager.RESPOND_EXTRA_POSITION_CLICKED, position); + final Bundle response = c.respond(clickResponse); + + // the convention is to send a position to select in response to a click (if applicable) + final int posToSelect = response.getInt( + SearchManager.RESPOND_EXTRA_POSITION_SELECTED, + SuggestionsAdapter.NO_ITEM_TO_SELECT); + mSuggestionsAdapter.setListItemToSelect(posToSelect); + + // launch the intent Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg); launchIntent(intent); + return true; } return false; } - + /** * Launches an intent. Also dismisses the search dialog if not in global search mode. */ @@ -1235,9 +1248,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.equals(action)) { handleChangeSourceIntent(intent); return true; - } else if (SearchManager.INTENT_ACTION_CURSOR_RESPOND.equals(action)) { - handleCursorRespondIntent(intent); - return true; } return false; } @@ -1268,34 +1278,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS String query = intent.getStringExtra(SearchManager.QUERY); setUserQuery(query); } - - /** - * Handles {@link SearchManager#INTENT_ACTION_CURSOR_RESPOND}. - */ - private void handleCursorRespondIntent(Intent intent) { - Cursor c = mSuggestionsAdapter.getCursor(); - if (c != null) { - Bundle response = c.respond(intent.getExtras()); - - // The SHOW_CORPUS_SELECTORS command to the cursor also returns a value in - // its bundle, keyed by the same command string, which contains the index - // of the "More results..." list item, which we use to instruct the - // AutoCompleteTextView's list to scroll to that item when the item is - // clicked. - if (response.containsKey(SuggestionsAdapter.SHOW_CORPUS_SELECTORS)) { - int indexOfMore = response.getInt(SuggestionsAdapter.SHOW_CORPUS_SELECTORS); - mSuggestionsAdapter.setListItemToSelect(indexOfMore); - } - } - } - + /** * Sets the list item selection in the AutoCompleteTextView's ListView. */ public void setListSelection(int index) { mSearchAutoComplete.setListSelection(index); } - + /** * Saves the previous component that was searched, so that we can go * back to it. @@ -1365,6 +1355,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS try { // use specific action if supplied, or default action if supplied, or fixed default String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION); + + // some items are display only, or have effect via the cursor respond click reporting. + if (SearchManager.INTENT_ACTION_NONE.equals(action)) { + return null; + } + if (action == null) { action = mSearchable.getSuggestIntentAction(); } @@ -1387,7 +1383,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS Uri dataUri = (data == null) ? null : Uri.parse(data); String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); - + String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY); return createIntent(action, dataUri, query, extraData, actionKey, actionMsg); diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index be2f50f..b4a3a78 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -1148,7 +1148,7 @@ public class SearchManager * @hide */ public final static String SOURCE = "source"; - + /** * Intent extra data key: Use this key with Intent.ACTION_SEARCH and * {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()} @@ -1162,10 +1162,44 @@ public class SearchManager /** * Intent extra data key: This key will be used for the extra populated by the * {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column. + * * {@hide} */ public final static String EXTRA_DATA_KEY = "intent_extra_data_key"; - + + + /** + * Used by the search dialog to ask the global search provider whether there are any pending + * sources that have yet to respond. Specifically, the search dialog will call + * {@link Cursor#respond} with a bundle containing this extra as a key, and expect the same key + * to be in the response, with a boolean value indicating whether there are pending sources. + * + * {@hide} + */ + public final static String RESPOND_EXTRA_PENDING_SOURCES = "respond_extra_pending_sources"; + + /** + * Used by the search dialog to tell the cursor that supplied suggestions which item was clicked + * before launching the intent. The search dialog will call {@link Cursor#respond} with a + * bundle containing this extra as a key and the position that was clicked as the value. + * + * The response bundle will use {@link #RESPOND_EXTRA_POSITION_SELECTED} to return an int value + * of the index that should be selected, if applicable. + * + * {@hide} + */ + public final static String RESPOND_EXTRA_POSITION_CLICKED = "respond_extra_position_clicked"; + + /** + * Used as a key in the response bundle from a call to {@link Cursor#respond} that sends the + * position that is clicked. + * + * @see #RESPOND_EXTRA_POSITION_CLICKED + * + * {@hide} + */ + public final static String RESPOND_EXTRA_POSITION_SELECTED = "respond_extra_position_selected"; + /** * Intent extra data key: Use this key with Intent.ACTION_SEARCH and * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()} @@ -1304,7 +1338,7 @@ public class SearchManager * to provide additional arbitrary data which will be included as an extra under the key * {@link #EXTRA_DATA_KEY}. For use by the global search system only - if other providers * attempt to use this column, the value will be overwritten by global search. - * + * * @hide */ public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data"; @@ -1363,21 +1397,7 @@ public class SearchManager */ public final static String INTENT_ACTION_CHANGE_SEARCH_SOURCE = "android.search.action.CHANGE_SEARCH_SOURCE"; - - /** - * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION}, - * the search dialog will call {@link Cursor#respond(Bundle)} when the - * suggestion is clicked. - * - * The {@link Bundle} argument will be constructed - * in the same way as the "extra" bundle included in an Intent constructed - * from the suggestion. - * - * @hide Pending API council approval. - */ - public final static String INTENT_ACTION_CURSOR_RESPOND - = "android.search.action.CURSOR_RESPOND"; - + /** * Intent action for finding the global search activity. * The global search provider should handle this intent. @@ -1395,6 +1415,14 @@ public class SearchManager */ public final static String INTENT_ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; + + /** + * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION}, + * the search dialog will take no action. + * + * @hide + */ + public final static String INTENT_ACTION_NONE = "android.search.action.ZILCH"; /** * Reference to the shared system search service. @@ -1506,25 +1534,27 @@ public class SearchManager } /** - * See {@link #setOnDismissListener} for configuring your activity to monitor search UI state. + * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor + * search UI state. */ public interface OnDismissListener { /** - * This method will be called when the search UI is dismissed. To make use if it, you must - * implement this method in your activity, and call {@link #setOnDismissListener} to - * register it. + * This method will be called when the search UI is dismissed. To make use of it, you must + * implement this method in your activity, and call + * {@link SearchManager#setOnDismissListener} to register it. */ public void onDismiss(); } /** - * See {@link #setOnCancelListener} for configuring your activity to monitor search UI state. + * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor + * search UI state. */ public interface OnCancelListener { /** * This method will be called when the search UI is canceled. To make use if it, you must - * implement this method in your activity, and call {@link #setOnCancelListener} to - * register it. + * implement this method in your activity, and call + * {@link SearchManager#setOnCancelListener} to register it. */ public void onCancel(); } diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java index 4406f8e..451697a 100644 --- a/core/java/android/app/SuggestionsAdapter.java +++ b/core/java/android/app/SuggestionsAdapter.java @@ -72,14 +72,16 @@ class SuggestionsAdapter extends ResourceCursorAdapter { private int mIconName2Col; private int mIconBitmap1Col; private int mIconBitmap2Col; - + // This value is stored in SuggestionsAdapter by the SearchDialog to indicate whether // a particular list item should be selected upon the next call to notifyDataSetChanged. // This is used to indicate the index of the "More results..." list item so that when // the data set changes after a click of "More results...", we can correctly tell the - // ListView to scroll to the right line item. It gets reset to -1 every time it is consumed. - private int mListItemToSelect = -1; - + // ListView to scroll to the right line item. It gets reset to NO_ITEM_TO_SELECT every time it + // is consumed. + private int mListItemToSelect = NO_ITEM_TO_SELECT; + static final int NO_ITEM_TO_SELECT = -1; + public SuggestionsAdapter(Context context, SearchDialog searchDialog, SearchableInfo searchable, WeakHashMap<String, Drawable> outsideDrawablesCache, boolean globalSearchMode) { super(context, @@ -146,9 +148,9 @@ class SuggestionsAdapter extends ResourceCursorAdapter { public void notifyDataSetChanged() { super.notifyDataSetChanged(); updateWorking(); - if (mListItemToSelect != -1) { + if (mListItemToSelect != NO_ITEM_TO_SELECT) { mSearchDialog.setListSelection(mListItemToSelect); - mListItemToSelect = -1; + mListItemToSelect = NO_ITEM_TO_SELECT; } } @@ -168,14 +170,12 @@ class SuggestionsAdapter extends ResourceCursorAdapter { if (!mGlobalSearchMode || mCursor == null) return; Bundle request = new Bundle(); - request.putString(SearchManager.EXTRA_DATA_KEY, IS_WORKING); + request.putString(SearchManager.RESPOND_EXTRA_PENDING_SOURCES, "DUMMY"); Bundle response = mCursor.respond(request); - if (response.containsKey(IS_WORKING)) { - boolean isWorking = response.getBoolean(IS_WORKING); - mSearchDialog.setWorking(isWorking); - } + + mSearchDialog.setWorking(response.getBoolean(SearchManager.RESPOND_EXTRA_PENDING_SOURCES)); } - + /** * Tags the view with cached child view look-ups. */ @@ -361,12 +361,16 @@ class SuggestionsAdapter extends ResourceCursorAdapter { // First, check the cache. Drawable drawable = mOutsideDrawablesCache.get(drawableId); - if (drawable != null) return drawable; + if (drawable != null) { + if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + drawableId); + return drawable; + } try { // Not cached, try using it as a plain resource ID in the provider's context. int resourceId = Integer.parseInt(drawableId); drawable = mProviderContext.getResources().getDrawable(resourceId); + if (DBG) Log.d(LOG_TAG, "Found icon by resource ID: " + drawableId); } catch (NumberFormatException nfe) { // The id was not an integer resource id. // Let the ContentResolver handle content, android.resource and file URIs. @@ -375,7 +379,9 @@ class SuggestionsAdapter extends ResourceCursorAdapter { drawable = Drawable.createFromStream( mProviderContext.getContentResolver().openInputStream(uri), null); + if (DBG) Log.d(LOG_TAG, "Opened icon input stream: " + drawableId); } catch (FileNotFoundException fnfe) { + if (DBG) Log.d(LOG_TAG, "Icon stream not found: " + drawableId); // drawable = null; } @@ -385,7 +391,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { mOutsideDrawablesCache.put(drawableId, drawable); } } catch (NotFoundException nfe) { - // Resource could not be found + if (DBG) Log.d(LOG_TAG, "Icon resource not found: " + drawableId); // drawable = null; } @@ -402,7 +408,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { */ public static String getColumnString(Cursor cursor, String columnName) { int col = cursor.getColumnIndex(columnName); - if (col == -1) { + if (col == NO_ITEM_TO_SELECT) { return null; } return cursor.getString(col); diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp index 9450e15..6c16150 100644 --- a/core/jni/android_os_MemoryFile.cpp +++ b/core/jni/android_os_MemoryFile.cpp @@ -82,9 +82,7 @@ static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz, return -1; } - jbyte* bytes = env->GetByteArrayElements(buffer, 0); - memcpy(bytes + destOffset, (const char *)address + srcOffset, count); - env->ReleaseByteArrayElements(buffer, bytes, 0); + env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset); if (unpinned) { ashmem_unpin_region(fd, 0, 0); @@ -103,9 +101,7 @@ static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz, return -1; } - jbyte* bytes = env->GetByteArrayElements(buffer, 0); - memcpy((char *)address + destOffset, bytes + srcOffset, count); - env->ReleaseByteArrayElements(buffer, bytes, 0); + env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset); if (unpinned) { ashmem_unpin_region(fd, 0, 0); diff --git a/core/res/res/drawable/stat_sys_vp_phone_call_bluetooth.png b/core/res/res/drawable/stat_sys_vp_phone_call_bluetooth.png Binary files differnew file mode 100644 index 0000000..7abfd19 --- /dev/null +++ b/core/res/res/drawable/stat_sys_vp_phone_call_bluetooth.png diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 96369f4..f67f04c 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -18,7 +18,7 @@ */ --> <resources> - <drawable name="screen_background_light">#ffffffff</drawable> + <drawable name="screen_background_light">#fff9f9f9</drawable> <drawable name="screen_background_dark">#ff1a1a1a</drawable> <drawable name="status_bar_closed_default_background">#ff000000</drawable> <drawable name="status_bar_opened_default_background">#ff000000</drawable> @@ -37,7 +37,7 @@ <color name="dim_foreground_dark_inverse">#323232</color> <color name="dim_foreground_dark_inverse_disabled">#80323232</color> <color name="hint_foreground_dark">#808080</color> - <color name="background_light">#ffffffff</color> + <color name="background_light">#fff9f9f9</color> <color name="bright_foreground_light">#ff000000</color> <color name="bright_foreground_light_inverse">#ffffffff</color> <color name="bright_foreground_light_disabled">#80000000</color> @@ -58,7 +58,7 @@ <drawable name="editbox_dropdown_dark_frame">@drawable/editbox_dropdown_background_dark</drawable> <drawable name="editbox_dropdown_light_frame">@drawable/editbox_dropdown_background</drawable> - <drawable name="input_method_fullscreen_background">#ffffffff</drawable> + <drawable name="input_method_fullscreen_background">#fff9f9f9</drawable> <!-- For date picker widget --> <drawable name="selected_day_background">#ff0092f4</drawable> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7078bbf..e048ffe 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -148,6 +148,24 @@ <!-- Meaning: unknown. Example: Service was enabled for: Voice, PAD --> <string name="serviceClassPAD">PAD</string> + <!-- CDMA Roaming Indicator Strings (non ERI)--> <skip /> + <!-- Default roaming indicator text --> + <string name="roamingText0">Roaming Indicator On</string> + <string name="roamingText1">Roaming Indicator Off</string> + <string name="roamingText2">Roaming Indicator Flashing</string> + <string name="roamingText3">Out of Neighborhood</string> + <string name="roamingText4">Out of Building</string> + <string name="roamingText5">Roaming - Preferred System</string> + <string name="roamingText6">Roaming - Available System</string> + <string name="roamingText7">Roaming - Alliance Partner</string> + <string name="roamingText8">Roaming - Premium Partner</string> + <string name="roamingText9">Roaming - Full Service Functionality</string> + <string name="roamingText10">Roaming - Partial Service Functionality</string> + <string name="roamingText11">Roaming Banner On</string> + <string name="roamingText12">Roaming Banner Off</string> + <string name="roamingTextSearching">Searching for Service</string> + + <!-- {0} is one of "bearerServiceCode*" {1} is dialing number diff --git a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd index bec0e43..e2fc89c 100644 --- a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd +++ b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd @@ -34,11 +34,11 @@ page.title=Activity and Task Design Guidelines <li><a href=#tips>Design Tips <ol> <li><a href=#activity_not_reused_tip>Don't specify intent filters in an activity that won't be re-used</a></li> - <li><a href=#others_to_reuse_tip>Don't define your own URI schemes</a></li> - <li><a href=#reusing_tip>Handle where a re-used activity is missing</a></li> + <!-- <li><a href=#others_to_reuse_tip>Don't define your own URI schemes</a></li> --> + <li><a href=#reusing_tip>Handle case where no activity matches</a></li> <li><a href=#activity_launching_tip>Consider how to launch your activities</a></li> <li><a href=#activities_added_to_task_tip>Allow activities to add to current task</a></li> - <li><a href=#notifications_return_tip>Notifications should be easy to return from</a></li> + <li><a href=#notifications_get_back_tip>Notifications should let user easily get back</li> <li><a href=#use_notification_tip>Use the notification system</a></li> <li><a href=#taking_over_back_key>Don't take over BACK key unless you absolutely need to</a></li> </ol> @@ -49,7 +49,6 @@ page.title=Activity and Task Design Guidelines <ol> <li><a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a></li> - <li><a href="http://android-developers.blogspot.com/2009/05/activities-and-tasks.html">Activities and Tasks blog post</a></li> </ol> </div> @@ -630,12 +629,12 @@ page.title=Activity and Task Design Guidelines <p> When the user takes an action on some data, such as touching a mailto:info@example.com link, they are actually initiating an Intent - object which then gets resolved to a particular component (we will - consider only activity components here). So, the result of a user - touching a mailto: link is an Intent object that the system tries to - match to an activity. If that Intent object was written explicitly - naming an activity (an <em>explicit intent</em>), then the system - immediately launches that activity in response to the user + object, or just an <em>intent</em>, which then gets resolved to a + particular component (we consider only activity components here). + So, the result of a user touching a mailto: link is an Intent object + that the system tries to match to an activity. If that Intent object was + written explicitly naming an activity (an <em>explicit intent</em>), + then the system immediately launches that activity in response to the user action. However, if that Intent object was written without naming an activity (an <em>implicit intent</em>), the system compares the Intent object to the <em>intent filters</em> of available activities. If more @@ -872,12 +871,29 @@ page.title=Activity and Task Design Guidelines <p> Your applications can re-use activities made available from other - applications. In doing so, you cannot presume that external activity - will always be present — you must handle the case that the - external activity is not installed. Do this in the way you find most - appropriate, such as dimming the user control that accesses it (such - as a button or menu item), or displaying a message to the user that - sends them to the location to download it, such as the Market. + applications. In doing so, you cannot presume your intent will always + be resolved to a matching external activity — you must handle the case + where no application installed on the device can handle the intent. +</p> + +<p> + You can either test that an activity matches the intent, which you can do + before starting the activity, or catch an exception if starting the + activity fails. Both approaches are descibed in the blog posting + <a href="http://android-developers.blogspot.com/2009/01/can-i-use-this-intent.html">Can + I use this Intent?</a>. +</p> + +<p> + To test whether an intent can be resolved, your code can query the package manager. + The blog post provides an example in the isIntentAvailable() helper method. + You can perform this test when initializing the user interface. + For instance, you could disable the user control that initiates + the Intent object, or display a message to the user that lets them go + to a location, such as the Market, to download its application. + In this way, your code can start the activity (using either startActivity() + or startActivityForResult()) only if the intent has tested to resolve + to an activity that is actually present. </p> <h3 id=activity_launching_tip>Consider how you want your activities to be launched or used by other applications</h3> @@ -1054,15 +1070,14 @@ page.title=Activity and Task Design Guidelines </p> -<h3 id="notifications_return_tip">Notifications should be easy for the user to return from</h3> - -<p> - Applications that are in the background or haven't been run can - send out notifications to the user letting them know about events - of interest. For example, Calendar can send out notifications of - upcoming events, and Email can send out notifications when new - messages arrive. One of the user interface rules is that when the - user is in activity A and gets a notification for activity B and +<h3 id="notifications_get_back_tip">Notifications should let the user easily get back to the previous activity</h3> +<p> + Applications that are in the background or not running can have + services that send out notifications to the user letting them know about + events of interest. Two examples are Calendar, which can send out notifications of + upcoming events, and Email, which can send out notifications when new + messages arrive. One of the user interface guidelines is that when the + user is in activity A, gets a notification for activity B and picks that notification, when they press the BACK key, they should go back to activity A. </p> @@ -1108,11 +1123,11 @@ Notifications generally happen primarily in one of two ways: <ul> <li> - <b>The application has a dedicated activity for - notification</b> - For example, when the user receives a - Calendar notification, the act of selecting that + <b>The chosen activity is dedicated for notification only</b> - + For example, when the user receives a + Calendar notification, choosing that notification starts a special activity that displays a list - of upcoming calendar events — a view available only + of upcoming calendar events — this view is available only from the notification, not through the Calendar's own user interface. After viewing this upcoming event, to ensure that the user pressing the BACK key will return to the activity @@ -1140,25 +1155,25 @@ Notifications generally happen primarily in one of two ways: </li> <li> - <b>The user choosing the notification brings the activity to + <b>The chosen activity is not dedicated, but always comes to the foreground in its initial state</b> - For example, in - response to a notification, the Gmail application is brought - to the foreground presenting the list of conversations. You - do this by having the user's response to the notification - trigger an intent to launch the activity with the clear top - flag set. (That is, you put {@link + response to a notification, when the Gmail application comes + to the foreground, it always presents the list of conversations. + You can ensure this happens by setting a "clear top" flag in the + intent that the notification triggers. This ensures that when the + activity is launched, it displays its initial activity, preventing + Gmail from coming to the foreground in whatever state the user last + happened to be viewing it. (To do this, you put {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP - FLAG_ACTIVITY_CLEAR_TOP} in the intent you pass to - startActivity()). This prevents Gmail from coming to the - foreground in whatever state the user last happened to be - viewing it. + FLAG_ACTIVITY_CLEAR_TOP} in the intent you pass to startActivity()). </li> </ul> <p> There are other ways to handle notifications, such as bringing the - activity to the foreground set to display specific data, such as the - ongoing text message thread of a particular person. + activity to the foreground, set to display specific data, such as + displaying the text message thread for the person who just sent a + new text message. </p> <p> diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index 9e88d7e..141cc68 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -18,18 +18,18 @@ package android.graphics; import android.content.res.AssetManager; import android.content.res.Resources; -import android.util.TypedValue; import android.util.DisplayMetrics; +import android.util.TypedValue; import java.io.BufferedInputStream; +import java.io.FileDescriptor; import java.io.FileInputStream; -import java.io.InputStream; import java.io.IOException; -import java.io.FileDescriptor; +import java.io.InputStream; /** - * Creates Bitmap objects from various sources, including files, streams, - * and byte-arrays. + * Creates Bitmap objects from various sources, including files, streams, + * and byte-arrays. */ public class BitmapFactory { public static class Options { @@ -62,7 +62,7 @@ public class BitmapFactory { * Also, powers of 2 are often faster/easier for the decoder to honor. */ public int inSampleSize; - + /** * If this is non-null, the decoder will try to decode into this * internal configuration. If it is null, or the request cannot be met, @@ -71,7 +71,7 @@ public class BitmapFactory { * as if it has per-pixel alpha (requiring a config that also does). */ public Bitmap.Config inPreferredConfig; - + /** * If dither is true, the decoder will atttempt to dither the decoded * image. @@ -117,8 +117,6 @@ public class BitmapFactory { * explicitly make a copy of the input data, and keep that. Even if * sharing is allowed, the implementation may still decide to make a * deep copy of the input data. - * - * @hide pending API council approval */ public boolean inPurgeable; @@ -151,12 +149,12 @@ public class BitmapFactory { * If not know, or there is an error, it is set to null. */ public String outMimeType; - + /** * Temp storage to use for decoding. Suggest 16K or so. */ public byte[] inTempStorage; - + private native void requestCancel(); /** @@ -167,7 +165,7 @@ public class BitmapFactory { * if the operation is canceled. */ public boolean mCancel; - + /** * This can be called from another thread while this options object is * inside a decode... call. Calling this will notify the decoder that @@ -249,7 +247,7 @@ public class BitmapFactory { if (opts.inDensity == 0) { opts.inDensity = density == TypedValue.DENSITY_DEFAULT ? DisplayMetrics.DEFAULT_DENSITY : density; - } + } float scale = opts.inDensity / (float) DisplayMetrics.DEFAULT_DENSITY; if (opts.inScaled || isNinePatch) { @@ -291,7 +289,7 @@ public class BitmapFactory { */ public static Bitmap decodeResource(Resources res, int id, Options opts) { Bitmap bm = null; - + try { final TypedValue value = new TypedValue(); final InputStream is = res.openRawResource(id, value); @@ -306,7 +304,7 @@ public class BitmapFactory { } return bm; } - + /** * Decode an image referenced by a resource ID. * @@ -337,7 +335,7 @@ public class BitmapFactory { } return nativeDecodeByteArray(data, offset, length, opts); } - + /** * Decode an immutable bitmap from the specified byte array. * @@ -350,13 +348,13 @@ public class BitmapFactory { public static Bitmap decodeByteArray(byte[] data, int offset, int length) { return decodeByteArray(data, offset, length, null); } - + /** * Decode an input stream into a bitmap. If the input stream is null, or * cannot be used to decode a bitmap, the function returns null. * The stream's position will be where ever it was after the encoded data * was read. - * + * * @param is The input stream that holds the raw data to be decoded into a * bitmap. * @param outPadding If not null, return the padding rect for the bitmap if @@ -375,7 +373,7 @@ public class BitmapFactory { if (is == null) { return null; } - + // we need mark/reset to work properly if (!is.markSupported()) { @@ -413,7 +411,7 @@ public class BitmapFactory { * cannot be used to decode a bitmap, the function returns null. * The stream's position will be where ever it was after the encoded data * was read. - * + * * @param is The input stream that holds the raw data to be decoded into a * bitmap. * @return The decoded bitmap, or null if the image data could not be @@ -441,7 +439,7 @@ public class BitmapFactory { public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) { return nativeDecodeFileDescriptor(fd, outPadding, opts); } - + /** * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded * return null. The position within the descriptor will not be changed when @@ -453,7 +451,7 @@ public class BitmapFactory { public static Bitmap decodeFileDescriptor(FileDescriptor fd) { return nativeDecodeFileDescriptor(fd, null, null); } - + private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts); private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd, diff --git a/opengl/tests/lighting1709/Android.mk b/opengl/tests/lighting1709/Android.mk new file mode 100644 index 0000000..9563e61 --- /dev/null +++ b/opengl/tests/lighting1709/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := LightingTest +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/opengl/tests/lighting1709/AndroidManifest.xml b/opengl/tests/lighting1709/AndroidManifest.xml new file mode 100644 index 0000000..6c23d42 --- /dev/null +++ b/opengl/tests/lighting1709/AndroidManifest.xml @@ -0,0 +1,13 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.lightingtest"> + + <application> + <activity android:name="ClearActivity" android:label="LightingTest"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java new file mode 100644 index 0000000..3dc31cc --- /dev/null +++ b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.lightingtest; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import android.app.Activity; +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.util.Log; +import android.view.MotionEvent; + +public class ClearActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + instance = counter++; + Log.e("ClearActivity", ":::::: onCreate: instance" + instance + " is created"); + super.onCreate(savedInstanceState); + mGLView = new ClearGLSurfaceView(this); + setContentView(mGLView); + } + + @Override + protected void onPause() { + Log.e("ClearActivity", ":::::: instance" + instance + " onPause: is called"); + super.onPause(); + mGLView.onPause(); + } + + @Override + protected void onResume() { + Log.e("ClearActivity", ":::::: instance" + instance + " onResume: is called"); + super.onResume(); + mGLView.onResume(); + } + + @Override + protected void onStop() { + Log.e("ClearActivity", ":::::: instance" + instance + " onStop: is called"); + super.onStop(); + } + + @Override + protected void onDestroy() { + Log.e("ClearActivity", ":::::: instance" + instance + " onDestroy: is called"); + super.onDestroy(); + } + + private GLSurfaceView mGLView; + + private static int counter = 0; + private int instance; +} + +class ClearGLSurfaceView extends GLSurfaceView { + public ClearGLSurfaceView(Context context) { + super(context); + instance = counter++; + Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " is created"); + mRenderer = new ClearRenderer(); + setRenderer(mRenderer); + } + + public boolean onTouchEvent(final MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: {// falling through on purpose here + Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onTouchEvent: handling down or move action"); + queueEvent(new Runnable(){ + public void run() { + mRenderer.setColor(event.getX() / getWidth(), + event.getY() / getHeight(), 1.0f); + }} + ); + return true; + } + case MotionEvent.ACTION_UP: { + // launch a second instance of the same activity + Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onTouchEvent: handling up action"); + // Intent intent = new Intent(); + // intent.setClass(getContext(), ClearActivity.class); + // getContext().startActivity(intent); + } + + } + return true; + } + + @Override + protected void onDetachedFromWindow() { + Log.e("ClearGLSurfaceView", ":::::: instance" + instance + " onDetachedFromWindow: is called"); + super.onDetachedFromWindow(); + } + + ClearRenderer mRenderer; + + private static int counter = 0; + private int instance; +} + +class ClearRenderer implements GLSurfaceView.Renderer { + public ClearRenderer() { + instance = counter++; + Log.e("ClearRenderer", ":::::: instance" + instance + " is created"); + } + + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + // Do nothing special. + Log.e("ClearRenderer", ":::::: instance" + instance + " onSurfaceCreated: is called"); + } + + public void onSurfaceChanged(GL10 gl, int w, int h) { + Log.e("ClearRenderer", ":::::: instance" + instance + " onSurfaceChanged: is called"); + + // Compute the projection matrix + gl.glMatrixMode(GL10.GL_PROJECTION); + gl.glLoadIdentity(); + + // Compute the boundaries of the frustum + float fl = (float) (-(w / 2)) / 288; + float fr = (float) (w / 2) / 288; + float ft = (float) (h / 2) / 288; + float fb = (float) (-(h / 2)) / 288; + + // Set the view frustum + gl.glFrustumf(fl, fr, fb, ft, 1.0f, 2000.0f); + + // Set the viewport dimensions + gl.glMatrixMode(GL10.GL_MODELVIEW); + gl.glLoadIdentity(); + gl.glViewport(0, 0, w, h); + } + + public void onDrawFrame(GL10 gl) { + // gl.glClearColor(mRed, mGreen, mBlue, 1.0f); + gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); + + float lightOff[] = {0.0f, 0.0f, 0.0f, 1.0f}; + float lightAmbient[] = {5.0f, 0.0f, 0.0f, 1.0f}; + float lightDiffuse[] = {0.0f, 2.0f, 0.0f, 0.0f}; + float lightPosAmbient[] = {0.0f, 0.0f, 0.0f, 1.0f}; + float lightPosSpot[] = {0.0f, 0.0f, -8.0f, 1.0f}; + + + float v[] = new float[9]; + ByteBuffer vbb = ByteBuffer.allocateDirect(v.length*4); + vbb.order(ByteOrder.nativeOrder()); + FloatBuffer vb = vbb.asFloatBuffer(); + + gl.glDisable(GL10.GL_DITHER); + + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, lightOff, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightOff, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosAmbient, 0); + gl.glEnable(GL10.GL_LIGHT0); + + gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_SPECULAR, lightOff, 0); + gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_DIFFUSE, lightDiffuse, 0); + gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_AMBIENT, lightOff, 0); + gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_POSITION, lightPosSpot, 0); + gl.glLightf(GL10.GL_LIGHT1, GL10.GL_CONSTANT_ATTENUATION, 1.0f); + gl.glLightf(GL10.GL_LIGHT1, GL10.GL_LINEAR_ATTENUATION, 0.0f); + gl.glLightf(GL10.GL_LIGHT1, GL10.GL_QUADRATIC_ATTENUATION, 0.022f); + gl.glEnable(GL10.GL_LIGHT1); + + gl.glEnable(GL10.GL_LIGHTING); + + // draw upper left triangle + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + v[0] = -6f; v[1] = 0.5f; v[2] = -10f; + v[3] = -5f; v[4] = 2.5f; v[5] = -10f; + v[6] = -4f; v[7] = 0.5f; v[8] = -10f; + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + gl.glNormal3f(0, 0, 1); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + + // draw upper middle triangle + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + v[0] = -1f; v[1] = 0.5f; v[2] = -10f; + v[3] = 0f; v[4] = 2.5f; v[5] = -10f; + v[6] = 1f; v[7] = 0.5f; v[8] = -10f; + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + gl.glNormal3f(0, 0, 1); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + + // draw upper right triangle + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + v[0] = 4f; v[1] = 0.5f; v[2] = -10f; + v[3] = 5f; v[4] = 2.5f; v[5] = -10f; + v[6] = 6f; v[7] = 0.5f; v[8] = -10f; + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + gl.glNormal3f(0, 0, 1); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + + // draw lower left triangle + gl.glPushMatrix(); + gl.glTranslatef(-5.0f, -1.5f, 0.0f); + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + v[0] = -1; v[1] = -1; v[2] = -10; + v[3] = 0; v[4] = 1; v[5] = -10; + v[6] = 1; v[7] = -1; v[8] = -10; + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + gl.glNormal3f(0, 0, 1); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + gl.glPopMatrix(); + + // draw lower middle triangle + gl.glPushMatrix(); + gl.glTranslatef(0.0f, -1.5f, 0.0f); + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + v[0] = -1; v[1] = -1; v[2] = -10; + v[3] = 0; v[4] = 1; v[5] = -10; + v[6] = 1; v[7] = -1; v[8] = -10; + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + gl.glNormal3f(0, 0, 1); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + gl.glPopMatrix(); + + // draw lower right triangle + gl.glPushMatrix(); + gl.glTranslatef(5.0f, -1.5f, 0.0f); + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + v[0] = -1; v[1] = -1; v[2] = -10; + v[3] = 0; v[4] = 1; v[5] = -10; + v[6] = 1; v[7] = -1; v[8] = -10; + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + gl.glNormal3f(0, 0, 1); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + gl.glPopMatrix(); + + } + + public int[] getConfigSpec() { + Log.e("ClearRenderer", ":::::: instance" + instance + " getConfigSpec: is called"); + int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE }; + return configSpec; + } + + public void setColor(float r, float g, float b) { + Log.e("ClearRenderer", ":::::: instance" + instance + " setColor: is called"); + mRed = r; + mGreen = g; + mBlue = b; + } + + private float mRed; + private float mGreen; + private float mBlue; + + private static int counter = 0; + private int instance; +} + diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java index af94100..42c885d 100644 --- a/services/java/com/android/server/status/StatusBarPolicy.java +++ b/services/java/com/android/server/status/StatusBarPolicy.java @@ -840,30 +840,10 @@ public class StatusBarPolicy { updateDataIcon(); } - // TODO(Teleca): I've add isCdma() to reduce some code duplication and simplify. - // Please validate the correctness of these changes private boolean isCdma() { - // Is this equivalent, if so it seems simpler? -// return ((mPhone != null) && (mPhone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA)); - - if (mServiceState != null) { - switch(mServiceState.getRadioTechnology()) { - case ServiceState.RADIO_TECHNOLOGY_1xRTT: - case ServiceState.RADIO_TECHNOLOGY_EVDO_0: - case ServiceState.RADIO_TECHNOLOGY_EVDO_A: - case ServiceState.RADIO_TECHNOLOGY_IS95A: - case ServiceState.RADIO_TECHNOLOGY_IS95B: - return true; - default: - return false; - } - } else { - return false; - } + return ((mPhone != null) && (mPhone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA)); } - // TODO(Teleca): I've add hasService() to reduce some code duplication and simplify. - // Please validate the correctness of these changes. private boolean hasService() { if (mServiceState != null) { switch (mServiceState.getState()) { @@ -1223,10 +1203,12 @@ public class StatusBarPolicy { private final void updateCdmaRoamingIcon() { if (!hasService()) { mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); + return; } if (!isCdma()) { mService.setIconVisibility(mCdmaRoamingIndicatorIcon, false); + return; } int[] iconList = sRoamingIndicatorImages_cdma; @@ -1256,8 +1238,10 @@ public class StatusBarPolicy { mService.setIconVisibility(mCdmaRoamingIndicatorIcon, true); break; case EriInfo.ROAMING_ICON_MODE_FLASH: - mCdmaRoamingIndicatorIconData.iconId = com.android.internal.R.drawable.stat_sys_roaming_cdma_flash; + mCdmaRoamingIndicatorIconData.iconId = + com.android.internal.R.drawable.stat_sys_roaming_cdma_flash; mService.updateIcon(mCdmaRoamingIndicatorIcon, mCdmaRoamingIndicatorIconData, null); + mService.setIconVisibility(mCdmaRoamingIndicatorIcon, true); break; } diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index e73de3c..f9b95b2 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -517,9 +517,14 @@ public class SmsMessage { return mWrappedSmsMessage.getUserData(); } - /* Not part of the SDK interface and only needed by specific classes: - protected SmsHeader getUserDataHeader() - */ + /** + * Return the user data header (UDH). + * + * @hide + */ + public SmsHeader getUserDataHeader() { + return mWrappedSmsMessage.getUserDataHeader(); + } /** * Returns the raw PDU for the message. diff --git a/telephony/java/com/android/internal/telephony/CommandsInterface.java b/telephony/java/com/android/internal/telephony/CommandsInterface.java index 34a57a0..c6d1a4b 100644 --- a/telephony/java/com/android/internal/telephony/CommandsInterface.java +++ b/telephony/java/com/android/internal/telephony/CommandsInterface.java @@ -1258,10 +1258,7 @@ public interface CommandsInterface { * @param result * Callback message is empty on completion */ - /** - * TODO(Teleca): configValuesArray is represented as a RIL_BroadcastSMSConfig - * so we think this should be a class with the appropriate parameters not an array? - */ + // TODO: Change the configValuesArray to a RIL_BroadcastSMSConfig public void setCdmaBroadcastConfig(int[] configValuesArray, Message result); /** diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java index d856279..d6b2737 100644 --- a/telephony/java/com/android/internal/telephony/PhoneBase.java +++ b/telephony/java/com/android/internal/telephony/PhoneBase.java @@ -53,15 +53,6 @@ import java.util.Locale; * */ -/** - * TODO(Teleca): This has a multitude of methods that are CDMA specific - * , (registerForVoicePrivacy, registerCdmaInformationRecord, registerCdmaCallWaiting, - * setCdmaRoamingPreference, setCdmaSubscription, getCdmaEriIcon, getCdmaEriText, ...) can - * these type of calls be more abstract. For example CallWaiting is common between the GSM/CDMA - * it would seem that doesn't need to be cdma specific. Also, should the application be directly - * dealing with the CdmaInformationRecord's could they be abstracted to something more generic. - */ - public abstract class PhoneBase implements Phone { private static final String LOG_TAG = "PHONE"; private static final boolean LOCAL_DEBUG = true; diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java index ea84b09..f206d90 100644 --- a/telephony/java/com/android/internal/telephony/RIL.java +++ b/telephony/java/com/android/internal/telephony/RIL.java @@ -3302,10 +3302,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { send(rr); } - /** - * TODO(Teleca): configValuesArray is represented as a RIL_BroadcastSMSConfig - * so we think this should be a class with the appropriate parameters not an array? - */ + // TODO: Change the configValuesArray to a RIL_BroadcastSMSConfig public void setCdmaBroadcastConfig(int[] configValuesArray, Message response) { RILRequest rr = RILRequest.obtain(RIL_REQUEST_CDMA_SET_BROADCAST_CONFIG, response); diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java index c0bfe5e..7ba9951 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -249,7 +249,7 @@ public class CDMAPhone extends PhoneBase { public DataActivityState getDataActivityState() { DataActivityState ret = DataActivityState.NONE; - if (mSST.getCurrentCdmaDataConnectionState() != ServiceState.RADIO_TECHNOLOGY_UNKNOWN) { + if (mSST.getCurrentCdmaDataConnectionState() == ServiceState.STATE_IN_SERVICE) { switch (mDataConnection.getActivity()) { case DATAIN: @@ -384,11 +384,11 @@ public class CDMAPhone extends PhoneBase { } public String getLine1Number() { - return mRuimRecords.getMdnNumber(); + return mSST.getMdnNumber(); } public String getCdmaMIN() { - return mRuimRecords.getCdmaMin(); + return mSST.getCdmaMin(); } public void getCallWaiting(Message onComplete) { @@ -545,8 +545,7 @@ public class CDMAPhone extends PhoneBase { // already been called ret = DataState.DISCONNECTED; - } else if (mSST.getCurrentCdmaDataConnectionState() - == ServiceState.RADIO_TECHNOLOGY_UNKNOWN) { + } else if (mSST.getCurrentCdmaDataConnectionState() != ServiceState.STATE_IN_SERVICE) { // If we're out of service, open TCP sockets may still work // but no data will flow ret = DataState.DISCONNECTED; @@ -1159,61 +1158,7 @@ public class CDMAPhone extends PhoneBase { public int getCdmaEriIconIndex() { int roamInd = getServiceState().getCdmaRoamingIndicator(); int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator(); - int ret = -1; - - switch (roamInd) { - // Handling the standard roaming indicator (non-ERI) - case EriInfo.ROAMING_INDICATOR_ON: - case EriInfo.ROAMING_INDICATOR_OFF: - case EriInfo.ROAMING_INDICATOR_FLASH: - Log.d(LOG_TAG, "Using Standard Roaming Indicator (non-ERI): " + roamInd); - ret = roamInd; - break; - - // Handling the Enhanced Roaming Indicator (roamInd > 2) - default: - if (!mEriManager.isEriFileLoaded()) { - /** - * TODO(Teleca): What is going on here? Conditionals on the variable being - * switched? Seems unreasonably confusing... Especially since the above comment - * indicates this should always be true... If we used explicit returns, the - * switch could be used to filter specific cases for early bail, and the rest - * could then be dealt with outside the switch... - */ - - if(defRoamInd > 2) { - Log.d(LOG_TAG, "ERI File not loaded, using: " - + EriInfo.ROAMING_INDICATOR_FLASH); - ret = EriInfo.ROAMING_INDICATOR_FLASH; - } else { - Log.d(LOG_TAG, "ERI File not loaded, using: " + defRoamInd); - ret = defRoamInd; - } - } else if (mEriManager.getEriInfo(roamInd) == null) { - if(mEriManager.getEriInfo(defRoamInd) == null) { -/** - * TODO(Teleca): Why the redundant code? Especially since it results in this very strange looking - * almost-identical conditional... How about calling each version of mEriManager.getEriInfo just - * once, and conditionalizing on the results.. - */ - Log.e(LOG_TAG, "Error: ERI entry: " + roamInd - + " not present, defRoamInd: " + defRoamInd - + " not defined in ERI file"); - ret = EriInfo.ROAMING_INDICATOR_ON; - } else { - int iconIndex = mEriManager.getEriInfo(defRoamInd).mIconIndex; - Log.d(LOG_TAG, "ERI entry " + roamInd + " not present, using icon: " - + iconIndex); - ret = iconIndex; - } - } else { - int iconIndex = mEriManager.getEriInfo(roamInd).mIconIndex; - Log.d(LOG_TAG, "Using ERI icon: " + iconIndex); - ret = iconIndex; - } - break; - } - return ret; + return mEriManager.getCdmaEriIconIndex(roamInd, defRoamInd); } /** @@ -1225,60 +1170,7 @@ public class CDMAPhone extends PhoneBase { public int getCdmaEriIconMode() { int roamInd = getServiceState().getCdmaRoamingIndicator(); int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator(); - int ret = -1; - - switch (roamInd) { - // Handling the standard roaming indicator (non-ERI) - case EriInfo.ROAMING_INDICATOR_ON: - case EriInfo.ROAMING_INDICATOR_OFF: - Log.d(LOG_TAG, "Using Standard Roaming Indicator (non-ERI): normal"); - ret = EriInfo.ROAMING_ICON_MODE_NORMAL; - break; - - case EriInfo.ROAMING_INDICATOR_FLASH: - Log.d(LOG_TAG, "Using Standard Roaming Indicator (non-ERI): flashing"); - ret = EriInfo.ROAMING_ICON_MODE_FLASH; - break; - - // Handling the Enhanced Roaming Indicator (roamInd > 2) - default: - if (!mEriManager.isEriFileLoaded()) { - if(defRoamInd > 2) { - Log.d(LOG_TAG, "ERI File not loaded, defRoamInd > 2, flashing"); - ret = EriInfo.ROAMING_ICON_MODE_FLASH; - } else { - switch (defRoamInd) { - // Handling the standard roaming indicator (non-ERI) - case EriInfo.ROAMING_INDICATOR_ON: - case EriInfo.ROAMING_INDICATOR_OFF: - Log.d(LOG_TAG, "ERI File not loaded, normal"); - ret = EriInfo.ROAMING_ICON_MODE_NORMAL; - break; - - case EriInfo.ROAMING_INDICATOR_FLASH: - Log.d(LOG_TAG, "ERI File not loaded, normal"); - ret = EriInfo.ROAMING_ICON_MODE_FLASH; - break; - } - } - } else if (mEriManager.getEriInfo(roamInd) == null) { - if(mEriManager.getEriInfo(defRoamInd) == null) { - Log.e(LOG_TAG, "Error: defRoamInd not defined in ERI file, normal"); - ret = EriInfo.ROAMING_ICON_MODE_NORMAL; - } else { - int mode = mEriManager.getEriInfo(defRoamInd).mIconMode; - Log.d(LOG_TAG, "ERI entry " + roamInd + " not present, icon mode: " - + mode); - ret = mode; - } - } else { - int mode = mEriManager.getEriInfo(roamInd).mIconMode; - Log.d(LOG_TAG, "Using ERI icon mode: " + mode); - ret = mode; - } - break; - } - return ret; + return mEriManager.getCdmaEriIconMode(roamInd, defRoamInd); } /** @@ -1288,94 +1180,6 @@ public class CDMAPhone extends PhoneBase { public String getCdmaEriText() { int roamInd = getServiceState().getCdmaRoamingIndicator(); int defRoamInd = getServiceState().getCdmaDefaultRoamingIndicator(); - String ret = "ERI text"; - - switch (roamInd) { - // Handling the standard roaming indicator (non-ERI) - case EriInfo.ROAMING_INDICATOR_ON: - ret = EriInfo.ROAMING_TEXT_0; - break; - case EriInfo.ROAMING_INDICATOR_OFF: - ret = EriInfo.ROAMING_TEXT_1; - break; - case EriInfo.ROAMING_INDICATOR_FLASH: - ret = EriInfo.ROAMING_TEXT_2; - break; - - // Handling the standard ERI - case 3: - ret = EriInfo.ROAMING_TEXT_3; - break; - case 4: - ret = EriInfo.ROAMING_TEXT_4; - break; - case 5: - ret = EriInfo.ROAMING_TEXT_5; - break; - case 6: - ret = EriInfo.ROAMING_TEXT_6; - break; - case 7: - ret = EriInfo.ROAMING_TEXT_7; - break; - case 8: - ret = EriInfo.ROAMING_TEXT_8; - break; - case 9: - ret = EriInfo.ROAMING_TEXT_9; - break; - case 10: - ret = EriInfo.ROAMING_TEXT_10; - break; - case 11: - ret = EriInfo.ROAMING_TEXT_11; - break; - case 12: - ret = EriInfo.ROAMING_TEXT_12; - break; - - // Handling the non standard Enhanced Roaming Indicator (roamInd > 63) - default: - if (!mEriManager.isEriFileLoaded()) { - if(defRoamInd > 2) { - Log.d(LOG_TAG, "ERI File not loaded, defRoamInd > 2, " + - EriInfo.ROAMING_TEXT_2); - ret = EriInfo.ROAMING_TEXT_2; - } else { - switch (defRoamInd) { - // Handling the standard roaming indicator (non-ERI) - case EriInfo.ROAMING_INDICATOR_ON: - Log.d(LOG_TAG, "ERI File not loaded, " + EriInfo.ROAMING_TEXT_0); - ret = EriInfo.ROAMING_TEXT_0; - break; - case EriInfo.ROAMING_INDICATOR_OFF: - Log.d(LOG_TAG, "ERI File not loaded, " + EriInfo.ROAMING_TEXT_1); - ret = EriInfo.ROAMING_TEXT_1; - break; - case EriInfo.ROAMING_INDICATOR_FLASH: - Log.d(LOG_TAG, "ERI File not loaded, " + EriInfo.ROAMING_TEXT_2); - ret = EriInfo.ROAMING_TEXT_2; - break; - } - } - } else if (mEriManager.getEriInfo(roamInd) == null) { - if(mEriManager.getEriInfo(defRoamInd) == null) { - Log.e(LOG_TAG, "Error: defRoamInd not defined in ERI file, " - + EriInfo.ROAMING_TEXT_0); - ret = EriInfo.ROAMING_TEXT_0; - } else { - String eriText = mEriManager.getEriInfo(defRoamInd).mEriText; - Log.d(LOG_TAG, "ERI entry " + roamInd + " not present, eri text: " - + eriText); - ret = eriText; - } - } else { - String eriText = mEriManager.getEriInfo(roamInd).mEriText; - Log.d(LOG_TAG, "Using ERI text: " + eriText); - ret = eriText; - } - break; - } - return ret; + return mEriManager.getCdmaEriText(roamInd, defRoamInd); } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CallFailCause.java b/telephony/java/com/android/internal/telephony/cdma/CallFailCause.java index 9af245c..fb5f0fa 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CallFailCause.java +++ b/telephony/java/com/android/internal/telephony/cdma/CallFailCause.java @@ -29,21 +29,12 @@ public interface CallFailCause { // Busy Tone static final int USER_BUSY = 17; - // TODO(Teleca): Should we remove commented out values? -// // No Tone -// static final int NUMBER_CHANGED = 22; -// static final int STATUS_ENQUIRY = 30; static final int NORMAL_UNSPECIFIED = 31; -// -// // Congestion Tone + + // Congestion Tone static final int NO_CIRCUIT_AVAIL = 34; -// static final int TEMPORARY_FAILURE = 41; -// static final int SWITCHING_CONGESTION = 42; -// static final int CHANNEL_NOT_AVAIL = 44; -// static final int QOS_NOT_AVAIL = 49; -// static final int BEARER_NOT_AVAIL = 58; -// -// // others + + // others static final int ACM_LIMIT_EXCEEDED = 68; static final int CALL_BARRED = 240; static final int FDN_BLOCKED = 241; diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java index d8a6a50..b92e9e4 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java @@ -361,9 +361,7 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { boolean desiredPowerState = mCdmaPhone.mSST.getDesiredPowerState(); if ((state == State.IDLE || state == State.SCANNING) - && (psState == ServiceState.RADIO_TECHNOLOGY_1xRTT || - psState == ServiceState.RADIO_TECHNOLOGY_EVDO_0 || - psState == ServiceState.RADIO_TECHNOLOGY_EVDO_A) + && (psState == ServiceState.STATE_IN_SERVICE) && ((phone.mCM.getRadioState() == CommandsInterface.RadioState.NV_READY) || mCdmaPhone.mRuimRecords.getRecordsLoaded()) && (mCdmaPhone.mSST.isConcurrentVoiceAndData() || diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java index 8ecdecd..a9c810d 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java @@ -69,36 +69,26 @@ import java.util.TimeZone; * {@hide} */ final class CdmaServiceStateTracker extends ServiceStateTracker { + //***** Instance Variables CDMAPhone phone; CdmaCellLocation cellLoc; CdmaCellLocation newCellLoc; /** - * TODO(Teleca): I don't think the initialization to -1 for all of these are - * really necessary, I don't seem them in GsmServiceStateTracker. Also, - * all of the other initialization is unnecessary as I believe Java guarantees - * 0, false & null, but if you think it's better than do all of them there are - * a few that aren't initialized. - */ - - /** * The access technology currently in use: DATA_ACCESS_ */ private int networkType = 0; private int newNetworkType = 0; private boolean mCdmaRoaming = false; - private int mRoamingIndicator = -1; - private int mIsInPrl = -1; - private int mDefaultRoamingIndicator = -1; + private int mRoamingIndicator; + private boolean mIsInPrl; + private int mDefaultRoamingIndicator; - /** - * TODO(Teleca): Maybe these should be initialized to STATE_OUT_OF_SERVICE like gprsState - * in GsmServiceStateTracker and remove the comment. - */ - private int cdmaDataConnectionState = -1; // Initially we assume no data connection - private int newCdmaDataConnectionState = -1; // Initially we assume no data connection + // Initially we assume no data connection + private int cdmaDataConnectionState = ServiceState.STATE_OUT_OF_SERVICE; + private int newCdmaDataConnectionState = ServiceState.STATE_OUT_OF_SERVICE; private int mRegistrationState = -1; private RegistrantList cdmaDataConnectionAttachedRegistrants = new RegistrantList(); private RegistrantList cdmaDataConnectionDetachedRegistrants = new RegistrantList(); @@ -117,7 +107,7 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { // We can't register for SIM_RECORDS_LOADED immediately because the // SIMRecords object may not be instantiated yet. - private boolean mNeedToRegForRuimLoaded; + private boolean mNeedToRegForRuimLoaded = false; // Wake lock used while setting time of day. private PowerManager.WakeLock mWakeLock; @@ -125,22 +115,20 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { // Keep track of SPN display rules, so we only broadcast intent if something changes. private String curSpn = null; - private String curEriText = null; + private String curPlmn = null; // it contains the name of the registered network in CDMA can + // be the ONS or ERI text private int curSpnRule = 0; - private String mMdn = null; - private int mHomeSystemId = -1; - private int mHomeNetworkId = -1; - private String mMin = null; + private String mMdn; + private int mHomeSystemId; + private int mHomeNetworkId; + private String mMin; + private boolean isEriTextLoaded = false; private boolean isSubscriptionFromRuim = false; - /** - * TODO(Teleca): Is this purely for debugging purposes, or do we expect this string to be - * passed around (eg, to the UI)? If the latter, it would be better to pass around a - * reasonCode, and let the UI provide its own strings. - */ - private String mRegistrationDeniedReason = null; + // Registration Denied Reason, General/Authentication Failure, used only for debugging purposes + private String mRegistrationDeniedReason; //***** Constants static final String LOG_TAG = "CDMA"; @@ -243,9 +231,7 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { Registrant r = new Registrant(h, what, obj); cdmaDataConnectionAttachedRegistrants.add(r); - if (cdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_1xRTT - || cdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_EVDO_0 - || cdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_EVDO_A) { + if (cdmaDataConnectionState == ServiceState.STATE_IN_SERVICE) { r.notifyRegistrant(); } } @@ -264,9 +250,7 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { Registrant r = new Registrant(h, what, obj); cdmaDataConnectionDetachedRegistrants.add(r); - if (cdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_1xRTT - && cdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_EVDO_0 - && cdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_EVDO_A) { + if (cdmaDataConnectionState != ServiceState.STATE_IN_SERVICE) { r.notifyRegistrant(); } } @@ -484,30 +468,40 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { } protected void updateSpnDisplay() { + String spn = ""; + boolean showSpn = false; + String plmn = ""; + boolean showPlmn = false; + int rule = 0; + if (cm.getRadioState().isRUIMReady()) { + // TODO RUIM SPN is not implemnted, EF_SPN has to be read and Display Condition + // Character Encoding, Language Indicator and SPN has to be set + // rule = phone.mRuimRecords.getDisplayRule(ss.getOperatorNumeric()); + // spn = phone.mSIMRecords.getServiceProvideName(); + plmn = ss.getOperatorAlphaLong(); // mOperatorAlphaLong contains the ONS + // showSpn = (rule & ... + showPlmn = true; // showPlmn = (rule & ... - // TODO(Teleca): Check this method again, because it is not sure at the moment how - // the RUIM handles the SIM stuff. Please complete this function. - - //int rule = phone.mRuimRecords.getDisplayRule(ss.getOperatorNumeric()); - String spn = null; //phone.mRuimRecords.getServiceProviderName(); - String eri = ss.getOperatorAlphaLong(); + } else { + // In this case there is no SPN available from RUIM, we show the ERI text + plmn = ss.getOperatorAlphaLong(); // mOperatorAlphaLong contains the ERI text + showPlmn = true; + } - if (!TextUtils.equals(this.curEriText, eri)) { - //TODO (rule & SIMRecords.SPN_RULE_SHOW_SPN) == SIMRecords.SPN_RULE_SHOW_SPN; - boolean showSpn = false; - //TODO (rule & SIMRecords.SPN_RULE_SHOW_PLMN) == SIMRecords.SPN_RULE_SHOW_PLMN; - boolean showEri = true; + if (rule != curSpnRule + || !TextUtils.equals(spn, curSpn) + || !TextUtils.equals(plmn, curPlmn)) { Intent intent = new Intent(Intents.SPN_STRINGS_UPDATED_ACTION); intent.putExtra(Intents.EXTRA_SHOW_SPN, showSpn); intent.putExtra(Intents.EXTRA_SPN, spn); - intent.putExtra(Intents.EXTRA_SHOW_PLMN, showEri); - intent.putExtra(Intents.EXTRA_PLMN, eri); + intent.putExtra(Intents.EXTRA_SHOW_PLMN, showPlmn); + intent.putExtra(Intents.EXTRA_PLMN, plmn); phone.getContext().sendStickyBroadcast(intent); } - //curSpnRule = rule; - //curSpn = spn; - this.curEriText = eri; + curSpnRule = rule; + curSpn = spn; + curPlmn = plmn; } /** @@ -549,54 +543,39 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { } } else try { switch (what) { - case EVENT_POLL_STATE_REGISTRATION_CDMA: // Handle RIL_REQUEST_REGISTRATION_STATE, - // the offset is because we don't want the - // first 3 values in the - // responseValuesRegistrationState array. - final int offset = 3; + case EVENT_POLL_STATE_REGISTRATION_CDMA: // Handle RIL_REQUEST_REGISTRATION_STATE. states = (String[])ar.result; - /** - * TODO(Teleca): Change from array to a "Class" or local - * variables so names instead of index's can be used. - */ - int responseValuesRegistrationState[] = { - -1, //[0] radioTechnology - -1, //[1] baseStationId - -1, //[2] baseStationLatitude - -1, //[3] baseStationLongitude - 0, //[4] cssIndicator; init with 0, because it is treated as a boolean - -1, //[5] systemId - -1, //[6] networkId - -1, //[7] Roaming indicator - -1, //[8] Indicates if current system is in PRL - -1, //[9] Is default roaming indicator from PRL - -1, //[10] If registration state is 3 this is reason for denial - }; + int registrationState = 4; //[0] registrationState + int radioTechnology = -1; //[3] radioTechnology + int baseStationId = -1; //[4] baseStationId + int baseStationLatitude = -1; //[5] baseStationLatitude + int baseStationLongitude = -1; //[6] baseStationLongitude + int cssIndicator = 0; //[7] init with 0, because it is treated as a boolean + int systemId = 0; //[8] systemId + int networkId = 0; //[9] networkId + int roamingIndicator = -1; //[10] Roaming indicator + int systemIsInPrl = 0; //[11] Indicates if current system is in PRL + int defaultRoamingIndicator = 0; //[12] Is default roaming indicator from PRL + int reasonForDenial = 0; //[13] Denial reason if registrationState = 3 if (states.length == 14) { try { - this.mRegistrationState = Integer.parseInt(states[0]); - } catch (NumberFormatException ex) { - Log.w(LOG_TAG, "error parsing RegistrationState: " + ex); - } - try { - responseValuesRegistrationState[0] = Integer.parseInt(states[3]); - responseValuesRegistrationState[1] = Integer.parseInt(states[4], 16); - responseValuesRegistrationState[2] = Integer.parseInt(states[5], 16); - responseValuesRegistrationState[3] = Integer.parseInt(states[6], 16); - responseValuesRegistrationState[4] = Integer.parseInt(states[7]); - responseValuesRegistrationState[5] = Integer.parseInt(states[8]); - responseValuesRegistrationState[6] = Integer.parseInt(states[9]); - responseValuesRegistrationState[7] = Integer.parseInt(states[10]); - responseValuesRegistrationState[8] = Integer.parseInt(states[11]); - responseValuesRegistrationState[9] = Integer.parseInt(states[12]); - responseValuesRegistrationState[10] = Integer.parseInt(states[13]); + registrationState = Integer.parseInt(states[0]); + radioTechnology = Integer.parseInt(states[3]); + baseStationId = Integer.parseInt(states[4], 16); + baseStationLatitude = Integer.parseInt(states[5], 16); + baseStationLongitude = Integer.parseInt(states[6], 16); + cssIndicator = Integer.parseInt(states[7]); + systemId = Integer.parseInt(states[8]); + networkId = Integer.parseInt(states[9]); + roamingIndicator = Integer.parseInt(states[10]); + systemIsInPrl = Integer.parseInt(states[11]); + defaultRoamingIndicator = Integer.parseInt(states[12]); + reasonForDenial = Integer.parseInt(states[13]); } catch(NumberFormatException ex) { - Log.w(LOG_TAG, "Warning! There is an unexpected value" - + "returned as response from " - + "RIL_REQUEST_REGISTRATION_STATE."); + Log.w(LOG_TAG, "error parsing RegistrationState: " + ex); } } else { throw new RuntimeException("Warning! Wrong number of parameters returned from " @@ -604,29 +583,28 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { + states.length); } - mCdmaRoaming = regCodeIsRoaming(this.mRegistrationState); - this.newCdmaDataConnectionState = - radioTechnologyToServiceState(responseValuesRegistrationState[0]); - newSS.setState (regCodeToServiceState(this.mRegistrationState)); - newSS.setRadioTechnology(responseValuesRegistrationState[0]); - newSS.setCssIndicator(responseValuesRegistrationState[4]); - newSS.setSystemAndNetworkId(responseValuesRegistrationState[5], - responseValuesRegistrationState[6]); + mRegistrationState = registrationState; + mCdmaRoaming = regCodeIsRoaming(registrationState); + newSS.setState (regCodeToServiceState(registrationState)); + + this.newCdmaDataConnectionState = radioTechnologyToDataServiceState(radioTechnology); + newSS.setRadioTechnology(radioTechnology); + newNetworkType = radioTechnology; - mRoamingIndicator = responseValuesRegistrationState[7]; - mIsInPrl = responseValuesRegistrationState[8]; - mDefaultRoamingIndicator = responseValuesRegistrationState[9]; + newSS.setCssIndicator(cssIndicator); + newSS.setSystemAndNetworkId(systemId, networkId); + mRoamingIndicator = roamingIndicator; + mIsInPrl = (systemIsInPrl == 0) ? false : true; + mDefaultRoamingIndicator = defaultRoamingIndicator; - newNetworkType = responseValuesRegistrationState[0]; // values are -1 if not available - newCellLoc.setCellLocationData(responseValuesRegistrationState[1], - responseValuesRegistrationState[2], - responseValuesRegistrationState[3]); + newCellLoc.setCellLocationData(baseStationId, baseStationLatitude, + baseStationLongitude); - if (responseValuesRegistrationState[10] == 0) { + if (reasonForDenial == 0) { mRegistrationDeniedReason = ServiceStateTracker.REGISTRATION_DENIED_GEN; - } else if (responseValuesRegistrationState[10] == 1) { + } else if (reasonForDenial == 1) { mRegistrationDeniedReason = ServiceStateTracker.REGISTRATION_DENIED_AUTH; } else { mRegistrationDeniedReason = ""; @@ -641,9 +619,7 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { String opNames[] = (String[])ar.result; if (opNames != null && opNames.length >= 3) { - // TODO(Teleca): Is this necessary here and in the else clause? - newSS.setOperatorName(opNames[0], opNames[1], opNames[2]); - if (phone.mCM.getRadioState().isNVReady()) { + if (cm.getRadioState().isNVReady()) { // In CDMA in case on NV the ss.mOperatorAlphaLong is set later with the // ERI text, so here is ignored what is coming from the modem newSS.setOperatorName(null, opNames[1], opNames[2]); @@ -695,28 +671,25 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { newSS.setRoaming(mCdmaRoaming); } - /** - * TODO(Teleca): This would be simpler if mIsInPrl was a "boolean" as the - * name implies rather than tri-state. Above I've suggested that the -1's - * might be able to be removed, if so please simplify this. Otherwise change - * the name to mPrlState or some such. Also the logic can be simplified - * by testing for "mIsInPrl" only once. - */ // Setting SS CdmaRoamingIndicator and CdmaDefaultRoamingIndicator - // TODO(Teleca): use constants for the standard roaming indicators - if (mIsInPrl == 0 && mRegistrationState == 5) { - // System is acquired but prl not loaded or no prl match - newSS.setCdmaRoamingIndicator(2); //FLASHING - } else if (!namMatch && (mIsInPrl == 1)) { - // System is acquired, no nam match, prl match - newSS.setCdmaRoamingIndicator(mRoamingIndicator); - } else if (namMatch && (mIsInPrl == 1) && mRoamingIndicator <= 2) { - // System is acquired, nam match, prl match, mRoamingIndicator <= 2 - newSS.setCdmaRoamingIndicator(1); //OFF - } else if (namMatch && (mIsInPrl == 1) && mRoamingIndicator > 2) { - // System is acquired, nam match, prl match, mRoamingIndicator > 2 - newSS.setCdmaRoamingIndicator(mRoamingIndicator); - } + // TODO(Teleca): Validate this is correct. + if (mIsInPrl) { + if (namMatch && (mRoamingIndicator <= 2)) { + // System is acquired, prl match, nam match and mRoamingIndicator <= 2 + newSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_OFF); + } else { + // System is acquired, prl match, no nam match or mRoamingIndicator > 2 + newSS.setCdmaRoamingIndicator(mRoamingIndicator); + } + } else { + if (mRegistrationState == 5) { + // System is acquired but prl not loaded or no prl match + newSS.setCdmaRoamingIndicator(EriInfo.ROAMING_INDICATOR_FLASH); + } else { + // Use the default indicator + } + } + newSS.setCdmaDefaultRoamingIndicator(mDefaultRoamingIndicator); // NOTE: Some operator may require to override the mCdmaRoaming (set by the modem) @@ -725,7 +698,7 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { if (DBG) { log("Set CDMA Roaming Indicator to: " + newSS.getCdmaRoamingIndicator() + ". mCdmaRoaming = " + mCdmaRoaming + ", namMatch = " + namMatch - + ", mIsInPrl= " + mIsInPrl + ", mRoamingIndicator = " + mRoamingIndicator + + ", mIsInPrl = " + mIsInPrl + ", mRoamingIndicator = " + mRoamingIndicator + ", mDefaultRoamingIndicator= " + mDefaultRoamingIndicator); } pollStateDone(); @@ -882,20 +855,12 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { && newSS.getState() != ServiceState.STATE_IN_SERVICE; boolean hasCdmaDataConnectionAttached = - (this.cdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_1xRTT - && this.cdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_EVDO_0 - && this.cdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_EVDO_A) - && (this.newCdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_1xRTT - || this.newCdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_EVDO_0 - || this.newCdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_EVDO_A); + this.cdmaDataConnectionState != ServiceState.STATE_IN_SERVICE + && this.newCdmaDataConnectionState == ServiceState.STATE_IN_SERVICE; boolean hasCdmaDataConnectionDetached = - (this.cdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_1xRTT - || this.cdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_EVDO_0 - || this.cdmaDataConnectionState == ServiceState.RADIO_TECHNOLOGY_EVDO_A) - && (this.newCdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_1xRTT - && this.newCdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_EVDO_0 - && this.newCdmaDataConnectionState != ServiceState.RADIO_TECHNOLOGY_EVDO_A); + this.cdmaDataConnectionState == ServiceState.STATE_IN_SERVICE + && this.newCdmaDataConnectionState != ServiceState.STATE_IN_SERVICE; boolean hasCdmaDataConnectionChanged = cdmaDataConnectionState != newCdmaDataConnectionState; @@ -938,18 +903,16 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { } if (hasChanged) { - if (phone.mCM.getRadioState().isNVReady()) { + if (cm.getRadioState().isNVReady()) { String eriText; // Now the CDMAPhone sees the new ServiceState so it can get the new ERI text if (ss.getState() == ServiceState.STATE_IN_SERVICE) { eriText = phone.getCdmaEriText(); } else { - // Note that this is valid only for mRegistrationState 2,3,4, not 0! - /** - * TODO(Teleca): From the comment this apparently isn't always true - * should there be additional logic with other strings? - */ - eriText = EriInfo.SEARCHING_TEXT; + // Note that ServiceState.STATE_OUT_OF_SERVICE is valid used for + // mRegistrationState 0,2,3 and 4 + eriText = phone.getContext().getText( + com.android.internal.R.string.roamingTextSearching).toString(); } ss.setCdmaEriText(eriText); } @@ -1047,6 +1010,12 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { return guess; } + /** + * TODO: This code is exactly the same as in GsmServiceStateTracker + * and has a TODO to not poll signal strength if screen is off. + * This code should probably be hoisted to the base class so + * the fix, when added, works for both. + */ private void queueNextSignalStrengthPoll() { if (dontPollSignalStrength || (cm.getRadioState().isGsm())) { @@ -1060,7 +1029,7 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { msg = obtainMessage(); msg.what = EVENT_POLL_SIGNAL_STRENGTH; - // TODO(Teleca): Don't poll signal strength if screen is off + // TODO Don't poll signal strength if screen is off sendMessageDelayed(msg, POLL_PERIOD_MILLIS); } @@ -1108,8 +1077,8 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { } - private int radioTechnologyToServiceState(int code) { - int retVal = ServiceState.RADIO_TECHNOLOGY_UNKNOWN; + private int radioTechnologyToDataServiceState(int code) { + int retVal = ServiceState.STATE_OUT_OF_SERVICE; switch(code) { case 0: case 1: @@ -1118,14 +1087,10 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { case 4: case 5: break; - case 6: - retVal = ServiceState.RADIO_TECHNOLOGY_1xRTT; - break; - case 7: - retVal = ServiceState.RADIO_TECHNOLOGY_EVDO_0; - break; - case 8: - retVal = ServiceState.RADIO_TECHNOLOGY_EVDO_A; + case 6: // RADIO_TECHNOLOGY_1xRTT + case 7: // RADIO_TECHNOLOGY_EVDO_0 + case 8: // RADIO_TECHNOLOGY_EVDO_A + retVal = ServiceState.STATE_IN_SERVICE; break; default: Log.e(LOG_TAG, "Wrong radioTechnology code."); @@ -1435,4 +1400,12 @@ final class CdmaServiceStateTracker extends ServiceStateTracker { Log.d(LOG_TAG, "[CdmaServiceStateTracker] " + s); } + public String getMdnNumber() { + return mMdn; + } + + public String getCdmaMin() { + return mMin; + } + } diff --git a/telephony/java/com/android/internal/telephony/cdma/EriInfo.java b/telephony/java/com/android/internal/telephony/cdma/EriInfo.java index 40358c8..5c8e23e 100644 --- a/telephony/java/com/android/internal/telephony/cdma/EriInfo.java +++ b/telephony/java/com/android/internal/telephony/cdma/EriInfo.java @@ -23,28 +23,6 @@ public final class EriInfo { public static final int ROAMING_ICON_MODE_NORMAL = 0; public static final int ROAMING_ICON_MODE_FLASH = 1; - /** - * TODO(Teleca): These strings appear to be used by the UI - * hence they must be changed to resources so they can be - * translated to the appropriate language. - */ - public static final String ROAMING_TEXT_0 = "Roaming Indicator On"; - public static final String ROAMING_TEXT_1 = "Roaming Indicator Off"; - public static final String ROAMING_TEXT_2 = "Roaming Indicator Flashing"; - public static final String ROAMING_TEXT_3 = "Out of Neighborhood"; - public static final String ROAMING_TEXT_4 = "Out of Building"; - public static final String ROAMING_TEXT_5 = "Roaming - Preferred System"; - public static final String ROAMING_TEXT_6 = "Roaming - Available System"; - public static final String ROAMING_TEXT_7 = "Roaming - Alliance Partner"; - public static final String ROAMING_TEXT_8 = "Roaming - Premium Partner"; - public static final String ROAMING_TEXT_9 = "Roaming - Full Service Functionality"; - public static final String ROAMING_TEXT_10 = "Roaming - Partial Service Functionality"; - public static final String ROAMING_TEXT_11 = "Roaming Banner On"; - public static final String ROAMING_TEXT_12 = "Roaming Banner Off"; - - public static final String SEARCHING_TEXT = "Searching for Svc."; - - public int mRoamingIndicator; public int mIconIndex; public int mIconMode; diff --git a/telephony/java/com/android/internal/telephony/cdma/EriManager.java b/telephony/java/com/android/internal/telephony/cdma/EriManager.java index d905e66..0997456 100644 --- a/telephony/java/com/android/internal/telephony/cdma/EriManager.java +++ b/telephony/java/com/android/internal/telephony/cdma/EriManager.java @@ -28,17 +28,12 @@ import com.android.internal.util.XmlUtils; import java.util.HashMap; /** - * TODO(Teleca): Please as some comments on how this class is to - * be used. We've removed Handler as a base class and instead - * recommend that child classes add a Handler as a member if its - * needed. + * EriManager loads the ERI file definitions and manages the CDMA roaming information. + * */ - - public final class EriManager { class EriFile { - public static final int MAX_ERI_ENTRIES = 30; public int mVersionNumber; // File version number public int mNumberOfEriEntries; // Number of entries @@ -55,7 +50,30 @@ public final class EriManager { this.mCallPromptId = new String[] { "", "", "" }; this.mRoamIndTable = new HashMap<Integer, EriInfo>(); } + } + + class EriDisplayInformation { + public int mEriIconIndex; + public int mEriIconMode; + public String mEriIconText; + + public EriDisplayInformation(int eriIconIndex, int eriIconMode, String eriIconText) { + mEriIconIndex = eriIconIndex; + mEriIconMode = eriIconMode; + mEriIconText = eriIconText; + } + +// public void setParameters(int eriIconIndex, int eriIconMode, String eriIconText){ +// this.mEriIconIndex = eriIconIndex; +// this.mEriIconMode = eriIconMode; +// this.mEriIconText = eriIconText; +// } + @Override + public String toString() { + return "EriDisplayInformation: {" + " IconIndex: " + mEriIconIndex + " EriIconMode: " + + mEriIconMode + " EriIconText: " + mEriIconText + " }"; + } } static final String LOG_TAG = "CDMA"; @@ -67,7 +85,7 @@ public final class EriManager { private PhoneBase mPhone; private Context mContext; private int mEriFileSource = ERI_FROM_XML; - private boolean isEriFileLoaded = false; + private boolean isEriFileLoaded; private EriFile mEriFile; public EriManager(PhoneBase phone, Context context, int eriFileSource) { @@ -214,11 +232,205 @@ public final class EriManager { * Returns the EriInfo record associated with roamingIndicator * or null if the entry is not found */ - public EriInfo getEriInfo(int roamingIndicator) { + private EriInfo getEriInfo(int roamingIndicator) { if (mEriFile.mRoamIndTable.containsKey(roamingIndicator)) { return mEriFile.mRoamIndTable.get(roamingIndicator); } else { return null; } } + + private EriDisplayInformation getEriDisplayInformation(int roamInd, int defRoamInd){ + //int iconIndex = -1; + //int iconMode = -1; + //String iconText = "ERI text"; + EriDisplayInformation ret; + + switch (roamInd) { + // Handling the standard roaming indicator (non-ERI) + case EriInfo.ROAMING_INDICATOR_ON: + ret = new EriDisplayInformation( + EriInfo.ROAMING_INDICATOR_ON, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText0).toString()); + break; + + case EriInfo.ROAMING_INDICATOR_OFF: + ret = new EriDisplayInformation( + EriInfo.ROAMING_INDICATOR_OFF, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText1).toString()); + break; + + case EriInfo.ROAMING_INDICATOR_FLASH: + ret = new EriDisplayInformation( + EriInfo.ROAMING_INDICATOR_FLASH, + EriInfo.ROAMING_ICON_MODE_FLASH, + mContext.getText(com.android.internal.R.string.roamingText2).toString()); + break; + + + // Handling the standard ERI + case 3: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText3).toString()); + break; + + case 4: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText4).toString()); + break; + + case 5: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText5).toString()); + break; + + case 6: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText6).toString()); + break; + + case 7: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText7).toString()); + break; + + case 8: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText8).toString()); + break; + + case 9: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText9).toString()); + break; + + case 10: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText10).toString()); + break; + + case 11: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText11).toString()); + break; + + case 12: + ret = new EriDisplayInformation( + roamInd, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal.R.string.roamingText12).toString()); + break; + + // Handling the non standard Enhanced Roaming Indicator (roamInd > 63) + default: + if (!isEriFileLoaded) { + // ERI file NOT loaded + Log.d(LOG_TAG, "ERI File not loaded"); + if(defRoamInd > 2) { + Log.d(LOG_TAG, "ERI defRoamInd > 2 ...flashing"); + ret = new EriDisplayInformation( + EriInfo.ROAMING_INDICATOR_FLASH, + EriInfo.ROAMING_ICON_MODE_FLASH, + mContext.getText(com.android.internal + .R.string.roamingText2).toString()); + } else { + Log.d(LOG_TAG, "ERI defRoamInd <= 2"); + switch (defRoamInd) { + case EriInfo.ROAMING_INDICATOR_ON: + ret = new EriDisplayInformation( + EriInfo.ROAMING_INDICATOR_ON, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal + .R.string.roamingText0).toString()); + break; + + case EriInfo.ROAMING_INDICATOR_OFF: + ret = new EriDisplayInformation( + EriInfo.ROAMING_INDICATOR_OFF, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal + .R.string.roamingText1).toString()); + break; + + case EriInfo.ROAMING_INDICATOR_FLASH: + ret = new EriDisplayInformation( + EriInfo.ROAMING_INDICATOR_FLASH, + EriInfo.ROAMING_ICON_MODE_FLASH, + mContext.getText(com.android.internal + .R.string.roamingText2).toString()); + break; + + default: + ret = new EriDisplayInformation(-1, -1, "ERI text"); + } + } + } else { + // ERI file loaded + Log.d(LOG_TAG, "ERI File loaded"); + EriInfo eriInfo = getEriInfo(roamInd); + EriInfo defEriInfo = getEriInfo(defRoamInd); + if (eriInfo == null) { + Log.d(LOG_TAG, "ERI roamInd " + roamInd + + " not found in ERI file ...using defRoamInd " + defRoamInd); + if(defEriInfo == null) { + Log.e(LOG_TAG, "ERI defRoamInd " + defRoamInd + + " not found in ERI file ...on"); + ret = new EriDisplayInformation( + EriInfo.ROAMING_INDICATOR_ON, + EriInfo.ROAMING_ICON_MODE_NORMAL, + mContext.getText(com.android.internal + .R.string.roamingText0).toString()); + + } else { + Log.d(LOG_TAG, "ERI defRoamInd " + defRoamInd + " found in ERI file"); + ret = new EriDisplayInformation( + defEriInfo.mIconIndex, + defEriInfo.mIconMode, + defEriInfo.mEriText); + } + } else { + Log.d(LOG_TAG, "ERI roamInd " + roamInd + " found in ERI file"); + ret = new EriDisplayInformation( + eriInfo.mIconIndex, + eriInfo.mIconMode, + eriInfo.mEriText); + } + } + break; + } + Log.d(LOG_TAG, "Displaying ERI " + ret.toString()); + return ret; + } + + public int getCdmaEriIconIndex(int roamInd, int defRoamInd){ + return getEriDisplayInformation(roamInd, defRoamInd).mEriIconIndex; + } + + public int getCdmaEriIconMode(int roamInd, int defRoamInd){ + return getEriDisplayInformation(roamInd, defRoamInd).mEriIconMode; + } + + public String getCdmaEriText(int roamInd, int defRoamInd){ + return getEriDisplayInformation(roamInd, defRoamInd).mEriIconText; + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java index d5b8379..7edd30f 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimRecords.java @@ -55,11 +55,8 @@ public final class RuimRecords extends IccRecords { //***** Instance Variables - private String mImsi; // TODO(Teleca): to be checked, if this should be removed! - private String mMyMobileNumber; - private String mSid; // TODO(Teleca): Unused should this be removed - private String mNid; // TODO(Teleca): Unused should this be removed - private String mMin2Min1; + String spn; + int spnDisplayCondition; //***** Event Constants @@ -134,19 +131,6 @@ public final class RuimRecords extends IccRecords { recordsRequested = false; } - /** Returns null if RUIM is not yet ready */ - public String getIMSI_M() { - return mImsi; - } - - public String getMdnNumber() { - return mMyMobileNumber; - } - - public String getCdmaMin() { - return mMin2Min1; - } - @Override public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete){ // In CDMA this is Operator/OEM dependent @@ -171,32 +155,6 @@ public final class RuimRecords extends IccRecords { } } - /** - * Returns the 5 or 6 digit MCC/MNC of the operator that - * provided the RUIM card. Returns null of RUIM is not yet ready - */ - public String getRUIMOperatorNumeric() { - if (mImsi == null) { - return null; - } - - if (mncLength != 0) { - // Length = length of MCC + length of MNC - // TODO: change spec name - // length of mcc = 3 (3GPP2 C.S0005 - Section 2.3) - return mImsi.substring(0, 3 + mncLength); - } - - // Guess the MNC length based on the MCC if we don't - // have a valid value in ef[ad] - - int mcc; - - mcc = Integer.parseInt(mImsi.substring(0,3)); - - return mImsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc)); - } - @Override public void handleMessage(Message msg) { AsyncResult ar; @@ -223,28 +181,35 @@ public final class RuimRecords extends IccRecords { /* IO events */ case EVENT_GET_CDMA_SUBSCRIPTION_DONE: + // TODO(Moto):TODO(Teleca): This event was removed by Teleca/QCT + // I've left it as it's needed to complete EVENT_OTA_PROVISION_STATUS_CHANGE. + // But since various instance variables are removed I've commented + // out code that references them. I'm sure this is wrong so + // Moto/Teleca/QCT need to come to an agreement. Also see onRuimReady + // and onVnReady. + ar = (AsyncResult)msg.obj; String localTemp[] = (String[])ar.result; if (ar.exception != null) { break; } if(m_ota_commited) { - if(mMyMobileNumber != localTemp[0]) { + //if(mMyMobileNumber != localTemp[0]) { Intent intent = new Intent(TelephonyIntents.ACTION_CDMA_OTA_MDN_CHANGED); intent.putExtra("mdn", localTemp[0]); Log.d(LOG_TAG,"Broadcasting intent MDN Change in OTA "); ActivityManagerNative.broadcastStickyIntent(intent, null); - } + //} m_ota_commited=false; } - mMyMobileNumber = localTemp[0]; - mSid = localTemp[1]; - mNid = localTemp[2]; - if (localTemp.length >= 3) { // TODO(Moto): remove when new ril always returns min2_min1 - mMin2Min1 = localTemp[3]; - } + //mMyMobileNumber = localTemp[0]; + //mSid = localTemp[1]; + //mNid = localTemp[2]; + //if (localTemp.length >= 3) { // TODO(Moto): remove when new ril always returns min2_min1 + // mMin2Min1 = localTemp[3]; + //} - Log.d(LOG_TAG, "MDN: " + mMyMobileNumber + " MIN: " + mMin2Min1); + //Log.d(LOG_TAG, "MDN: " + mMyMobileNumber + " MIN: " + mMin2Min1); break; @@ -350,14 +315,14 @@ public final class RuimRecords extends IccRecords { RuimCard.INTENT_VALUE_ICC_READY, null); fetchRuimRecords(); - - phone.mCM.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE)); - + + // TODO(Moto): TODO(Teleca): Work out how to do CDMA subscription + // phone.mCM.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE)); } private void onNvReady() { - phone.mCM.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE)); - + // TODO(Moto): TODO(Teleca): Work out how to do CDMA subscription + // phone.mCM.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE)); } private void fetchRuimRecords() { diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index b2083ed..9152559 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -356,39 +356,6 @@ public class SmsMessage extends SmsMessageBase { return privateGetSubmitPdu(destAddr, statusReportRequested, uData); } - static class PduParser { - - PduParser() { - } - - /** - * Parses an SC timestamp and returns a currentTimeMillis()-style - * timestamp - */ - static long getSCTimestampMillis(byte[] timestamp) { - // TP-Service-Centre-Time-Stamp - int year = IccUtils.beBcdByteToInt(timestamp[0]); - int month = IccUtils.beBcdByteToInt(timestamp[1]); - int day = IccUtils.beBcdByteToInt(timestamp[2]); - int hour = IccUtils.beBcdByteToInt(timestamp[3]); - int minute = IccUtils.beBcdByteToInt(timestamp[4]); - int second = IccUtils.beBcdByteToInt(timestamp[5]); - - Time time = new Time(Time.TIMEZONE_UTC); - - // C.S0015-B v2.0, 4.5.4: range is 1996-2095 - time.year = year >= 96 ? year + 1900 : year + 2000; - time.month = month - 1; - time.monthDay = day; - time.hour = hour; - time.minute = minute; - time.second = second; - - return time.toMillis(true); - } - - } - /** * Note: This function is a GSM specific functionality which is not supported in CDMA mode. */ @@ -557,8 +524,8 @@ public class SmsMessage extends SmsMessageBase { + originatingAddress.address); } - if (mBearerData.timeStamp != null) { - scTimeMillis = PduParser.getSCTimestampMillis(mBearerData.timeStamp); + if (mBearerData.msgCenterTimeStamp != null) { + scTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true); } if (Config.LOGD) Log.d(LOG_TAG, "SMS SC timestamp: " + scTimeMillis); diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index 05c8c9d..ab65b0a 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -20,6 +20,9 @@ import android.util.Log; import android.telephony.SmsMessage; +import android.text.format.Time; + +import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.cdma.sms.UserData; @@ -38,15 +41,16 @@ public final class BearerData{ /** * Bearer Data Subparameter Indentifiers * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1) + * NOTE: Commented subparameter types are not implemented. */ private final static byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00; private final static byte SUBPARAM_USER_DATA = 0x01; private final static byte SUBPARAM_USER_REPONSE_CODE = 0x02; private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP = 0x03; - //private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE = 0x04; - //private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE = 0x05; - //private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE = 0x06; - //private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE = 0x07; + private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE = 0x04; + private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE = 0x05; + private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE = 0x06; + private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE = 0x07; private final static byte SUBPARAM_PRIORITY_INDICATOR = 0x08; private final static byte SUBPARAM_PRIVACY_INDICATOR = 0x09; private final static byte SUBPARAM_REPLY_OPTION = 0x0A; @@ -56,7 +60,7 @@ public final class BearerData{ private final static byte SUBPARAM_CALLBACK_NUMBER = 0x0E; private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE = 0x0F; //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA = 0x10; - //private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11; + private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11; //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12; //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13; private final static byte SUBPARAM_MESSAGE_STATUS = 0x14; @@ -205,23 +209,94 @@ public final class BearerData{ */ public UserData userData; - //public UserResponseCode userResponseCode; + /** + * The User Response Code subparameter is used in the SMS User + * Acknowledgment Message to respond to previously received short + * messages. This message center-specific element carries the + * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2, + * 4.5.3) + */ + public boolean userResponseCodeSet = false; + public int userResponseCode; /** * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4 - * year, month, day, hours, minutes, seconds; */ - public byte[] timeStamp; + public static class TimeStamp extends Time { + + public TimeStamp() { + super(Time.TIMEZONE_UTC); + } - //public SmsTime validityPeriodAbsolute; - //public SmsRelTime validityPeriodRelative; - //public SmsTime deferredDeliveryTimeAbsolute; - //public SmsRelTime deferredDeliveryTimeRelative; + public static TimeStamp fromByteArray(byte[] data) { + TimeStamp ts = new TimeStamp(); + // C.S0015-B v2.0, 4.5.4: range is 1996-2095 + int year = IccUtils.beBcdByteToInt(data[0]); + if (year > 99 || year < 0) return null; + ts.year = year >= 96 ? year + 1900 : year + 2000; + int month = IccUtils.beBcdByteToInt(data[1]); + if (month < 1 || month > 12) return null; + ts.month = month - 1; + int day = IccUtils.beBcdByteToInt(data[2]); + if (day < 1 || day > 31) return null; + ts.monthDay = day; + int hour = IccUtils.beBcdByteToInt(data[3]); + if (hour < 0 || hour > 23) return null; + ts.hour = hour; + int minute = IccUtils.beBcdByteToInt(data[4]); + if (minute < 0 || minute > 59) return null; + ts.minute = minute; + int second = IccUtils.beBcdByteToInt(data[5]); + if (second < 0 || second > 59) return null; + ts.second = second; + return ts; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("TimeStamp "); + builder.append("{ year=" + year); + builder.append(", month=" + month); + builder.append(", day=" + monthDay); + builder.append(", hour=" + hour); + builder.append(", minute=" + minute); + builder.append(", second=" + second); + builder.append(" }"); + return builder.toString(); + } + } + + public TimeStamp msgCenterTimeStamp; + public TimeStamp validityPeriodAbsolute; + public TimeStamp deferredDeliveryTimeAbsolute; /** - * Reply Option - * 1-bit values which indicate whether SMS acknowledgment is requested or not. - * (See 3GPP2 C.S0015-B, v2, 4.5.11) + * Relative time is specified as one byte, the value of which + * falls into a series of ranges, as specified below. The idea is + * that shorter time intervals allow greater precision -- the + * value means minutes from zero until the MINS_LIMIT (inclusive), + * upon which it means hours until the HOURS_LIMIT, and so + * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1) + */ + public static final int RELATIVE_TIME_MINS_LIMIT = 143; + public static final int RELATIVE_TIME_HOURS_LIMIT = 167; + public static final int RELATIVE_TIME_DAYS_LIMIT = 196; + public static final int RELATIVE_TIME_WEEKS_LIMIT = 244; + public static final int RELATIVE_TIME_INDEFINITE = 245; + public static final int RELATIVE_TIME_NOW = 246; + public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247; + public static final int RELATIVE_TIME_RESERVED = 248; + + public boolean validityPeriodRelativeSet; + public int validityPeriodRelative; + public boolean deferredDeliveryTimeRelativeSet; + public int deferredDeliveryTimeRelative; + + /** + * The Reply Option subparameter contains 1-bit values which + * indicate whether SMS acknowledgment is requested or not. (See + * 3GPP2 C.S0015-B, v2, 4.5.11) */ public boolean userAckReq; public boolean deliveryAckReq; @@ -229,14 +304,28 @@ public final class BearerData{ public boolean reportReq; /** - * The number of Messages element (8-bit value) is a decimal number in the 0 to 99 range - * representing the number of messages stored at the Voice Mail System. This element is - * used by the Voice Mail Notification service. - * (See 3GPP2 C.S0015-B, v2, 4.5.12) + * The Number of Messages subparameter (8-bit value) is a decimal + * number in the 0 to 99 range representing the number of messages + * stored at the Voice Mail System. This element is used by the + * Voice Mail Notification service. (See 3GPP2 C.S0015-B, v2, + * 4.5.12) */ public int numberOfMessages; /** + * The Message Deposit Index subparameter is assigned by the + * message center as a unique index to the contents of the User + * Data subparameter in each message sent to a particular mobile + * station. The mobile station, when replying to a previously + * received short message which included a Message Deposit Index + * subparameter, may include the Message Deposit Index of the + * received message to indicate to the message center that the + * original contents of the message are to be included in the + * reply. (See 3GPP2 C.S0015-B, v2, 4.5.18) + */ + public int depositIndex; + + /** * 4-bit or 8-bit value that indicates the number to be dialed in reply to a * received SMS message. * (See 3GPP2 C.S0015-B, v2, 4.5.15) @@ -262,14 +351,23 @@ public final class BearerData{ builder.append(", language=" + (languageIndicatorSet ? language : "unset")); builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset")); builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset")); - builder.append(", timeStamp=" + - ((timeStamp != null) ? HexDump.toHexString(timeStamp) : "unset")); + builder.append(", msgCenterTimeStamp=" + + ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset")); + builder.append(", validityPeriodAbsolute=" + + ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset")); + builder.append(", validityPeriodRelative=" + + ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset")); + builder.append(", deferredDeliveryTimeAbsolute=" + + ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset")); + builder.append(", deferredDeliveryTimeRelative=" + + ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset")); builder.append(", userAckReq=" + userAckReq); builder.append(", deliveryAckReq=" + deliveryAckReq); builder.append(", readAckReq=" + readAckReq); builder.append(", reportReq=" + reportReq); builder.append(", numberOfMessages=" + numberOfMessages); builder.append(", callbackNumber=" + callbackNumber); + builder.append(", depositIndex=" + depositIndex); builder.append(", hasUserDataHeader=" + hasUserDataHeader); builder.append(", userData=" + userData); builder.append(" }"); @@ -518,11 +616,11 @@ public final class BearerData{ outStream.write(8, bData.numberOfMessages); } - private static void encodeMsgCenterTimeStamp(BearerData bData, BitwiseOutputStream outStream) + private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream) throws BitwiseOutputStream.AccessException { - outStream.write(8, 6); - outStream.writeByteArray(6 * 8, bData.timeStamp); + outStream.write(8, 1); + outStream.write(8, bData.validityPeriodRelative); } private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream) @@ -595,9 +693,9 @@ public final class BearerData{ outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES); encodeMsgCount(bData, outStream); } - if (bData.timeStamp != null) { - outStream.write(8, SUBPARAM_MESSAGE_CENTER_TIME_STAMP); - encodeMsgCenterTimeStamp(bData, outStream); + if (bData.validityPeriodRelativeSet) { + outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE); + encodeValidityPeriodRel(bData, outStream); } if (bData.privacyIndicatorSet) { outStream.write(8, SUBPARAM_PRIVACY_INDICATOR); @@ -791,6 +889,15 @@ public final class BearerData{ bData.numberOfMessages = inStream.read(8); } + private static void decodeDepositIndex(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 2) { + throw new CodingException("MESSAGE_DEPOSIT_INDEX subparam size incorrect"); + } + bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8); + } + private static String decodeDtmfSmsAddress(byte[] rawData, int numFields) throws CodingException { @@ -863,14 +970,51 @@ public final class BearerData{ bData.messageStatusSet = true; } - private static void decodeMsgCenterTimeStamp(BearerData bData, - BitwiseInputStream inStream) + private static void decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException { if (inStream.read(8) != 6) { throw new CodingException("MESSAGE_CENTER_TIME_STAMP subparam size incorrect"); } - bData.timeStamp = inStream.readByteArray(6 * 8); + bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); + } + + private static void decodeValidityAbs(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 6) { + throw new CodingException("VALIDITY_PERIOD_ABSOLUTE subparam size incorrect"); + } + bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); + } + + private static void decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 6) { + throw new CodingException("DEFERRED_DELIVERY_TIME_ABSOLUTE subparam size incorrect"); + } + bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); + } + + private static void decodeValidityRel(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 1) { + throw new CodingException("VALIDITY_PERIOD_RELATIVE subparam size incorrect"); + } + bData.deferredDeliveryTimeRelative = inStream.read(8); + bData.deferredDeliveryTimeRelativeSet = true; + } + + private static void decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 1) { + throw new CodingException("DEFERRED_DELIVERY_TIME_RELATIVE subparam size incorrect"); + } + bData.validityPeriodRelative = inStream.read(8); + bData.validityPeriodRelativeSet = true; } private static void decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream) @@ -927,6 +1071,16 @@ public final class BearerData{ bData.alertIndicatorSet = true; } + private static void decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream) + throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.read(8) != 1) { + throw new CodingException("USER_REPONSE_CODE subparam size incorrect"); + } + bData.userResponseCode = inStream.read(8); + bData.userResponseCodeSet = true; + } + /** * Create BearerData object from serialized representation. * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) @@ -955,6 +1109,9 @@ public final class BearerData{ case SUBPARAM_USER_DATA: decodeUserData(bData, inStream); break; + case SUBPARAM_USER_REPONSE_CODE: + decodeUserResponseCode(bData, inStream); + break; case SUBPARAM_REPLY_OPTION: decodeReplyOption(bData, inStream); break; @@ -970,6 +1127,18 @@ public final class BearerData{ case SUBPARAM_MESSAGE_CENTER_TIME_STAMP: decodeMsgCenterTimeStamp(bData, inStream); break; + case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE: + decodeValidityAbs(bData, inStream); + break; + case SUBPARAM_VALIDITY_PERIOD_RELATIVE: + decodeValidityRel(bData, inStream); + break; + case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE: + decodeDeferredDeliveryAbs(bData, inStream); + break; + case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE: + decodeDeferredDeliveryRel(bData, inStream); + break; case SUBPARAM_PRIVACY_INDICATOR: decodePrivacyIndicator(bData, inStream); break; @@ -985,6 +1154,9 @@ public final class BearerData{ case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY: decodeMsgDeliveryAlert(bData, inStream); break; + case SUBPARAM_MESSAGE_DEPOSIT_INDEX: + decodeDepositIndex(bData, inStream); + break; default: throw new CodingException("unsupported bearer data subparameter (" + subparamId + ")"); diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java index 440debb..917ec9d 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java @@ -20,23 +20,31 @@ import com.android.internal.telephony.SmsAddress; import com.android.internal.util.HexDump; public class CdmaSmsAddress extends SmsAddress { + /** - * digit mode indicators - * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) + * Digit Mode Indicator is a 1-bit value that indicates whether + * the address digits are 4-bit DTMF codes or 8-bit codes. (See + * 3GPP2 C.S0015-B, v2, 3.4.3.3) */ static public final int DIGIT_MODE_4BIT_DTMF = 0x00; static public final int DIGIT_MODE_8BIT_CHAR = 0x01; + public byte digitMode; + /** - * number mode indicators - * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) + * Number Mode Indicator is 1-bit value that indicates whether the + * address type is a data network address or not. (See 3GPP2 + * C.S0015-B, v2, 3.4.3.3) */ static public final int NUMBER_MODE_NOT_DATA_NETWORK = 0x00; static public final int NUMBER_MODE_DATA_NETWORK = 0x01; + public byte numberMode; + /** - * number types for data networks - * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) + * Number Types for data networks. + * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) + * NOTE: value is stored in the parent class ton field. */ static public final int TON_UNKNOWN = 0x00; static public final int TON_INTERNATIONAL_OR_IP = 0x01; @@ -48,14 +56,21 @@ public class CdmaSmsAddress extends SmsAddress { static public final int TON_RESERVED = 0x07; /** - * maximum lengths for fields as defined in ril_cdma_sms.h + * Maximum lengths for fields as defined in ril_cdma_sms.h. */ static public final int SMS_ADDRESS_MAX = 36; static public final int SMS_SUBADDRESS_MAX = 36; /** - * Supported numbering plan identification - * (See C.S005-D, v1.0, table 2.7.1.3.2.4-3) + * This field shall be set to the number of address digits + * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) + */ + public byte numberOfDigits; + + /** + * Numbering Plan identification is a 0 or 4-bit value that + * indicates which numbering plan identification is set. (See + * 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3) */ static public final int NUMBERING_PLAN_UNKNOWN = 0x0; static public final int NUMBERING_PLAN_ISDN_TELEPHONY = 0x1; @@ -63,50 +78,29 @@ public class CdmaSmsAddress extends SmsAddress { //static protected final int NUMBERING_PLAN_TELEX = 0x4; //static protected final int NUMBERING_PLAN_PRIVATE = 0x9; - /** - * 1-bit value that indicates whether the address digits are 4-bit DTMF codes - * or 8-bit codes. - * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) - */ - public byte digitMode; - - /** - * 1-bit value that indicates whether the address type is a data network address or not. - * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) - */ - public byte numberMode; - - // use parent class member ton instead public byte numberType; - - /** - * 0 or 4-bit value that indicates which numbering plan identification is set. - * (See 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3) - */ public byte numberPlan; /** - * This field shall be set to the number of address digits - * (See 3GPP2 C.S0015-B, v2, 3.4.3.3) + * NOTE: the parsed string address and the raw byte array values + * are stored in the parent class address and origBytes fields, + * respectively. */ - public byte numberOfDigits; - - // use parent class member orig_bytes instead of public byte[] digits; - // Constructor public CdmaSmsAddress(){ } @Override public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("CdmaSmsAddress:\n"); - builder.append(" digitMode: " + digitMode + "\n"); - builder.append(" numberMode: " + numberMode + "\n"); - builder.append(" numberPlan: " + numberPlan + "\n"); - builder.append(" numberOfDigits: " + numberOfDigits + "\n"); - builder.append(" ton: " + ton + "\n"); - builder.append(" address: " + address + "\n"); - builder.append(" origBytes: " + HexDump.toHexString(origBytes) + "\n"); + builder.append("CdmaSmsAddress "); + builder.append("{ digitMode=" + digitMode); + builder.append(", numberMode=" + numberMode); + builder.append(", numberPlan=" + numberPlan); + builder.append(", numberOfDigits=" + numberOfDigits); + builder.append(", ton=" + ton); + builder.append(", address=" + address); + builder.append(", origBytes=" + HexDump.toHexString(origBytes)); + builder.append(" }"); return builder.toString(); } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 1d918a3..6e4a495 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -151,7 +151,6 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { private static final int POLL_PDP_MILLIS = 5 * 1000; - //WINK:TODO: Teleca, is this really gsm specific, what about CDMA? private static final String INTENT_RECONNECT_ALARM = "com.android.internal.telephony.gprs-reconnect"; private static final String INTENT_RECONNECT_ALARM_EXTRA_REASON = "reason"; diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java index 7a4ea64..63b6a5e 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java @@ -75,13 +75,6 @@ import java.util.TimeZone; */ final class GsmServiceStateTracker extends ServiceStateTracker { - /** - * TODO(Teleca): John Huang asks: Will you be adding handling of - * "reason for registration denied in EVENT_POLL_STATE_REGISTRATION? - * I see some handling of this in CdmaServiceStateTracker, but as I - * understand it this field was added at the request of a GSM carrier. - */ - //***** Instance Variables GSMPhone phone; GsmCellLocation cellLoc; diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index bfdd8a7..6435be5 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -637,8 +637,6 @@ public class SmsMessage extends SmsMessageBase{ /** * Returns an object representing the user data headers * - * @return an object representing the user data headers - * * {@hide} */ SmsHeader getUserDataHeader() { diff --git a/telephony/java/com/android/internal/telephony/gsm/stk/StkService.java b/telephony/java/com/android/internal/telephony/gsm/stk/StkService.java index 3de14f0..8f0addc 100644 --- a/telephony/java/com/android/internal/telephony/gsm/stk/StkService.java +++ b/telephony/java/com/android/internal/telephony/gsm/stk/StkService.java @@ -184,9 +184,6 @@ public class StkService extends Handler implements AppInterface { mCmdIf.unSetOnStkCallSetUp(this); this.removeCallbacksAndMessages(null); - - //removing instance - sInstance = null; } protected void finalize() { @@ -450,7 +447,7 @@ public class StkService extends Handler implements AppInterface { } /** - * Used for instantiating the Service from the GsmPhone constructor. + * Used for instantiating/updating the Service from the GsmPhone constructor. * * @param ci CommandsInterface object * @param sr SIMRecords object diff --git a/tests/AndroidTests/src/com/android/unit_tests/CdmaSmsTest.java b/tests/AndroidTests/src/com/android/unit_tests/CdmaSmsTest.java index 75fd157..16aca4d 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/CdmaSmsTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/CdmaSmsTest.java @@ -33,7 +33,7 @@ import java.util.Iterator; import android.util.Log; public class CdmaSmsTest extends AndroidTestCase { - private final static String LOG_TAG = "Cdma_Sms_Test"; + private final static String LOG_TAG = "CDMA"; @SmallTest public void testUserData7bitGsm() throws Exception { @@ -136,6 +136,100 @@ public class CdmaSmsTest extends AndroidTestCase { } @SmallTest + public void testMonolithicOne() throws Exception { + String pdu = "0003200010010410168d2002010503060812011101590501c706069706180000000701c108" + + "01c00901800a01e00b01030c01c00d01070e05039acc13880f018011020566"; + BearerData bearerData = BearerData.decode(HexDump.hexStringToByteArray(pdu)); + assertEquals(bearerData.messageType, BearerData.MESSAGE_TYPE_SUBMIT); + assertEquals(bearerData.messageId, 1); + assertEquals(bearerData.priority, BearerData.PRIORITY_EMERGENCY); + assertEquals(bearerData.privacy, BearerData.PRIVACY_CONFIDENTIAL); + assertEquals(bearerData.userAckReq, true); + assertEquals(bearerData.readAckReq, true); + assertEquals(bearerData.deliveryAckReq, true); + assertEquals(bearerData.reportReq, false); + assertEquals(bearerData.numberOfMessages, 3); + assertEquals(bearerData.alert, BearerData.ALERT_HIGH_PRIO); + assertEquals(bearerData.language, BearerData.LANGUAGE_HEBREW); + assertEquals(bearerData.callbackNumber.digitMode, CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF); + assertEquals(bearerData.callbackNumber.numberMode, + CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK); + assertEquals(bearerData.callbackNumber.ton, CdmaSmsAddress.TON_UNKNOWN); + assertEquals(bearerData.callbackNumber.numberPlan, CdmaSmsAddress.NUMBERING_PLAN_UNKNOWN); + assertEquals(bearerData.callbackNumber.numberOfDigits, 7); + assertEquals(bearerData.callbackNumber.address, "3598271"); + assertEquals(bearerData.displayMode, BearerData.DISPLAY_MODE_USER); + assertEquals(bearerData.depositIndex, 1382); + assertEquals(bearerData.userResponseCode, 5); + assertEquals(bearerData.msgCenterTimeStamp.year, 2008); + assertEquals(bearerData.msgCenterTimeStamp.month, 11); + assertEquals(bearerData.msgCenterTimeStamp.monthDay, 1); + assertEquals(bearerData.msgCenterTimeStamp.hour, 11); + assertEquals(bearerData.msgCenterTimeStamp.minute, 1); + assertEquals(bearerData.msgCenterTimeStamp.second, 59); + assertEquals(bearerData.validityPeriodAbsolute, null); + assertEquals(bearerData.validityPeriodRelative, -63); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.year, 1997); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.month, 5); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthDay, 18); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.hour, 0); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.minute, 0); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.second, 0); + assertEquals(bearerData.deferredDeliveryTimeRelative, -57); + assertEquals(bearerData.hasUserDataHeader, false); + assertEquals(bearerData.userData.msgEncoding, UserData.ENCODING_7BIT_ASCII); + assertEquals(bearerData.userData.numFields, 2); + assertEquals(bearerData.userData.payloadStr, "hi"); + } + + @SmallTest + public void testMonolithicTwo() throws Exception { + String pdu = "0003200010010410168d200201050306081201110159050192060697061800000007013d0" + + "801c00901800a01e00b01030c01c00d01070e05039acc13880f018011020566"; + BearerData bearerData = BearerData.decode(HexDump.hexStringToByteArray(pdu)); + assertEquals(bearerData.messageType, BearerData.MESSAGE_TYPE_SUBMIT); + assertEquals(bearerData.messageId, 1); + assertEquals(bearerData.priority, BearerData.PRIORITY_EMERGENCY); + assertEquals(bearerData.privacy, BearerData.PRIVACY_CONFIDENTIAL); + assertEquals(bearerData.userAckReq, true); + assertEquals(bearerData.readAckReq, true); + assertEquals(bearerData.deliveryAckReq, true); + assertEquals(bearerData.reportReq, false); + assertEquals(bearerData.numberOfMessages, 3); + assertEquals(bearerData.alert, BearerData.ALERT_HIGH_PRIO); + assertEquals(bearerData.language, BearerData.LANGUAGE_HEBREW); + assertEquals(bearerData.callbackNumber.digitMode, CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF); + assertEquals(bearerData.callbackNumber.numberMode, + CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK); + assertEquals(bearerData.callbackNumber.ton, CdmaSmsAddress.TON_UNKNOWN); + assertEquals(bearerData.callbackNumber.numberPlan, CdmaSmsAddress.NUMBERING_PLAN_UNKNOWN); + assertEquals(bearerData.callbackNumber.numberOfDigits, 7); + assertEquals(bearerData.callbackNumber.address, "3598271"); + assertEquals(bearerData.displayMode, BearerData.DISPLAY_MODE_USER); + assertEquals(bearerData.depositIndex, 1382); + assertEquals(bearerData.userResponseCode, 5); + assertEquals(bearerData.msgCenterTimeStamp.year, 2008); + assertEquals(bearerData.msgCenterTimeStamp.month, 11); + assertEquals(bearerData.msgCenterTimeStamp.monthDay, 1); + assertEquals(bearerData.msgCenterTimeStamp.hour, 11); + assertEquals(bearerData.msgCenterTimeStamp.minute, 1); + assertEquals(bearerData.msgCenterTimeStamp.second, 59); + assertEquals(bearerData.validityPeriodAbsolute, null); + assertEquals(bearerData.validityPeriodRelative, 61); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.year, 1997); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.month, 5); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.monthDay, 18); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.hour, 0); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.minute, 0); + assertEquals(bearerData.deferredDeliveryTimeAbsolute.second, 0); + assertEquals(bearerData.deferredDeliveryTimeRelative, -110); + assertEquals(bearerData.hasUserDataHeader, false); + assertEquals(bearerData.userData.msgEncoding, UserData.ENCODING_7BIT_ASCII); + assertEquals(bearerData.userData.numFields, 2); + assertEquals(bearerData.userData.payloadStr, "hi"); + } + + @SmallTest public void testUserDataHeaderConcatRefFeedback() throws Exception { BearerData bearerData = new BearerData(); bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER; @@ -410,22 +504,6 @@ public class CdmaSmsTest extends AndroidTestCase { } @SmallTest - public void testMsgCenterTimeStampFeedback() throws Exception { - BearerData bearerData = new BearerData(); - bearerData.messageType = BearerData.MESSAGE_TYPE_DELIVER; - bearerData.messageId = 0; - bearerData.hasUserDataHeader = false; - UserData userData = new UserData(); - userData.payloadStr = "test message center timestamp"; - bearerData.userData = userData; - bearerData.timeStamp = HexDump.hexStringToByteArray("112233445566"); - byte []encodedSms = BearerData.encode(bearerData); - BearerData revBearerData = BearerData.decode(encodedSms); - assertEquals(HexDump.toHexString(bearerData.timeStamp), - HexDump.toHexString(revBearerData.timeStamp)); - } - - @SmallTest public void testPrivacyIndicator() throws Exception { String pdu1 = "0003104090010c485f4194dfea34becf61b840090140"; BearerData bd1 = BearerData.decode(HexDump.hexStringToByteArray(pdu1)); diff --git a/tts/java/android/tts/SynthProxy.java b/tts/java/android/tts/SynthProxy.java new file mode 100755 index 0000000..4ed9754 --- /dev/null +++ b/tts/java/android/tts/SynthProxy.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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.tts; + +import android.util.Log; +import java.lang.ref.WeakReference; + +/** + * @hide + * + * The SpeechSynthesis class provides a high-level api to create and play + * synthesized speech. This class is used internally to talk to a native + * TTS library that implements the interface defined in + * frameworks/base/include/tts/TtsEngine.h + * + */ +@SuppressWarnings("unused") +public class SynthProxy { + + // + // External API + // + + /** + * Constructor; pass the location of the native TTS .so to use. + */ + public SynthProxy(String nativeSoLib) { + Log.e("TTS is loading", nativeSoLib); + native_setup(new WeakReference<SynthProxy>(this), nativeSoLib); + } + + /** + * Stops and clears the AudioTrack. + */ + public void stop() { + native_stop(mJniData); + } + + /** + * Synthesize speech and speak it directly using AudioTrack. + */ + public void speak(String text) { + native_speak(mJniData, text); + } + + /** + * Synthesize speech to a file. The current implementation writes a valid + * WAV file to the given path, assuming it is writable. Something like + * "/sdcard/???.wav" is recommended. + */ + public void synthesizeToFile(String text, String filename) { + native_synthesizeToFile(mJniData, text, filename); + } + + // TODO add IPA methods + + /** + * Sets the language + */ + public void setLanguage(String language) { + native_setLanguage(mJniData, language); + } + + /** + * Sets the speech rate + */ + public final void setSpeechRate(int speechRate) { + native_setSpeechRate(mJniData, speechRate); + } + + + /** + * Plays the given audio buffer + */ + public void playAudioBuffer(int bufferPointer, int bufferSize) { + native_playAudioBuffer(mJniData, bufferPointer, bufferSize); + } + + /** + * Gets the currently set language + */ + public String getLanguage() { + return native_getLanguage(mJniData); + } + + /** + * Gets the currently set rate + */ + public int getRate() { + return native_getRate(mJniData); + } + + /** + * Shuts down the native synthesizer + */ + public void shutdown() { + native_shutdown(mJniData); + } + + // + // Internal + // + + protected void finalize() { + native_finalize(mJniData); + mJniData = 0; + } + + static { + System.loadLibrary("synthproxy"); + } + + private final static String TAG = "SynthProxy"; + + /** + * Accessed by native methods + */ + private int mJniData = 0; + + private native final void native_setup(Object weak_this, + String nativeSoLib); + + private native final void native_finalize(int jniData); + + private native final void native_stop(int jniData); + + private native final void native_speak(int jniData, String text); + + private native final void native_synthesizeToFile(int jniData, String text, String filename); + + private native final void native_setLanguage(int jniData, String language); + + private native final void native_setSpeechRate(int jniData, int speechRate); + + // TODO add buffer format + private native final void native_playAudioBuffer(int jniData, int bufferPointer, int bufferSize); + + private native final String native_getLanguage(int jniData); + + private native final int native_getRate(int jniData); + + private native final void native_shutdown(int jniData); + + + /** + * Callback from the C layer + */ + @SuppressWarnings("unused") + private static void postNativeSpeechSynthesizedInJava(Object tts_ref, + int bufferPointer, int bufferSize) { + + Log.i("TTS plugin debug", "bufferPointer: " + bufferPointer + + " bufferSize: " + bufferSize); + + SynthProxy nativeTTS = (SynthProxy)((WeakReference)tts_ref).get(); + // TODO notify TTS service of synthesis/playback completion, + // method definition to be changed. + } +} diff --git a/tts/java/android/tts/TtsService.java b/tts/java/android/tts/TtsService.java new file mode 100755 index 0000000..d317181 --- /dev/null +++ b/tts/java/android/tts/TtsService.java @@ -0,0 +1,783 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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.tts; + +import android.tts.ITts.Stub; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.util.Log; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @hide Synthesizes speech from text. This is implemented as a service so that + * other applications can call the TTS without needing to bundle the TTS + * in the build. + * + */ +public class TtsService extends Service implements OnCompletionListener { + + private static class SpeechItem { + public static final int SPEECH = 0; + public static final int EARCON = 1; + public static final int SILENCE = 2; + public String mText = null; + public ArrayList<String> mParams = null; + public int mType = SPEECH; + public long mDuration = 0; + + public SpeechItem(String text, ArrayList<String> params, int itemType) { + mText = text; + mParams = params; + mType = itemType; + } + + public SpeechItem(long silenceTime) { + mDuration = silenceTime; + } + } + + /** + * Contains the information needed to access a sound resource; the name of + * the package that contains the resource and the resID of the resource + * within that package. + */ + private static class SoundResource { + public String mSourcePackageName = null; + public int mResId = -1; + public String mFilename = null; + + public SoundResource(String packageName, int id) { + mSourcePackageName = packageName; + mResId = id; + mFilename = null; + } + + public SoundResource(String file) { + mSourcePackageName = null; + mResId = -1; + mFilename = file; + } + } + + private static final String ACTION = "android.intent.action.USE_TTS"; + private static final String CATEGORY = "android.intent.category.TTS"; + private static final String PKGNAME = "android.tts"; + + final RemoteCallbackList<ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>(); + + private Boolean mIsSpeaking; + private ArrayList<SpeechItem> mSpeechQueue; + private HashMap<String, SoundResource> mEarcons; + private HashMap<String, SoundResource> mUtterances; + private MediaPlayer mPlayer; + private TtsService mSelf; + + private SharedPreferences prefs; + + private final ReentrantLock speechQueueLock = new ReentrantLock(); + private final ReentrantLock synthesizerLock = new ReentrantLock(); + + // TODO support multiple SpeechSynthesis objects + private SynthProxy nativeSynth; + + @Override + public void onCreate() { + super.onCreate(); + Log.i("TTS", "TTS starting"); + + + // TODO: Make this work when the settings are done in the main Settings + // app. + prefs = PreferenceManager.getDefaultSharedPreferences(this); + + // TODO: This should be changed to work by requesting the path + // from the default engine. + nativeSynth = new SynthProxy(prefs.getString("engine_pref", "")); + + + mSelf = this; + mIsSpeaking = false; + + mEarcons = new HashMap<String, SoundResource>(); + mUtterances = new HashMap<String, SoundResource>(); + + mSpeechQueue = new ArrayList<SpeechItem>(); + mPlayer = null; + + setLanguage(prefs.getString("lang_pref", "en-rUS")); + setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140"))); + } + + @Override + public void onDestroy() { + super.onDestroy(); + // Don't hog the media player + cleanUpPlayer(); + + nativeSynth.shutdown(); + + // Unregister all callbacks. + mCallbacks.kill(); + } + + private void setSpeechRate(int rate) { + if (prefs.getBoolean("override_pref", false)) { + // This is set to the default here so that the preview in the prefs + // activity will show the change without a restart, even if apps are + // not allowed to change the defaults. + rate = Integer.parseInt(prefs.getString("rate_pref", "140")); + } + nativeSynth.setSpeechRate(rate); + } + + private void setLanguage(String lang) { + if (prefs.getBoolean("override_pref", false)) { + // This is set to the default here so that the preview in the prefs + // activity will show the change without a restart, even if apps are + // not + // allowed to change the defaults. + lang = prefs.getString("lang_pref", "en-rUS"); + } + nativeSynth.setLanguage(lang); + } + + private void setEngine(String engineName, String[] requestedLanguages, + int strictness) { + // TODO: Implement engine selection code here. + Intent engineIntent = new Intent( + "android.intent.action.START_TTS_ENGINE"); + if (engineName != null) { + engineIntent.addCategory("android.intent.action.tts_engine." + + engineName); + } + for (int i = 0; i < requestedLanguages.length; i++) { + engineIntent.addCategory("android.intent.action.tts_lang." + + requestedLanguages[i]); + } + ResolveInfo[] enginesArray = new ResolveInfo[0]; + PackageManager pm = getPackageManager(); + enginesArray = pm.queryIntentActivities(engineIntent, 0).toArray( + enginesArray); + } + + private void setEngine(Intent engineIntent) { + // TODO: Implement engine selection code here. + } + + private int getEngineStatus() { + // TODO: Proposal - add a sanity check method that + // TTS engine plugins must implement. + return 0; + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + private void addSpeech(String text, String packageName, int resId) { + mUtterances.put(text, new SoundResource(packageName, resId)); + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a complete + * path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + private void addSpeech(String text, String filename) { + mUtterances.put(text, new SoundResource(filename)); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + private void addEarcon(String earcon, String packageName, int resId) { + mEarcons.put(earcon, new SoundResource(packageName, resId)); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a complete + * path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + private void addEarcon(String earcon, String filename) { + mEarcons.put(earcon, new SoundResource(filename)); + } + + /** + * Speaks the given text using the specified queueing mode and parameters. + * + * @param text + * The text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. + */ + private void speak(String text, int queueMode, ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + /** + * Plays the earcon using the specified queueing mode and parameters. + * + * @param earcon + * The earcon that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. + */ + private void playEarcon(String earcon, int queueMode, + ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + /** + * Stops all speech output and removes any utterances still in the queue. + */ + private void stop() { + Log.i("TTS", "Stopping"); + mSpeechQueue.clear(); + + nativeSynth.stop(); + mIsSpeaking = false; + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } + } + Log.i("TTS", "Stopped"); + } + + public void onCompletion(MediaPlayer arg0) { + processSpeechQueue(); + } + + private void playSilence(long duration, int queueMode, + ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(duration)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + private void silence(final long duration) { + class SilenceThread implements Runnable { + public void run() { + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + processSpeechQueue(); + } + } + } + Thread slnc = (new Thread(new SilenceThread())); + slnc.setPriority(Thread.MIN_PRIORITY); + slnc.start(); + } + + private void speakInternalOnly(final String text, + final ArrayList<String> params) { + class SynthThread implements Runnable { + public void run() { + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + Thread.sleep(100); + Thread synth = (new Thread(new SynthThread())); + synth.setPriority(Thread.MIN_PRIORITY); + synth.start(); + return; + } + nativeSynth.speak(text); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + // This check is needed because finally will always run; + // even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + } + } + Thread synth = (new Thread(new SynthThread())); + synth.setPriority(Thread.MIN_PRIORITY); + synth.start(); + } + + private SoundResource getSoundResource(SpeechItem speechItem) { + SoundResource sr = null; + String text = speechItem.mText; + if (speechItem.mType == SpeechItem.SILENCE) { + // Do nothing if this is just silence + } else if (speechItem.mType == SpeechItem.EARCON) { + sr = mEarcons.get(text); + } else { + sr = mUtterances.get(text); + } + return sr; + } + + private void dispatchSpeechCompletedCallbacks(String mark) { + Log.i("TTS callback", "dispatch started"); + // Broadcast to all clients the new value. + final int N = mCallbacks.beginBroadcast(); + for (int i = 0; i < N; i++) { + try { + mCallbacks.getBroadcastItem(i).markReached(mark); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + mCallbacks.finishBroadcast(); + Log.i("TTS callback", "dispatch completed to " + N); + } + + private void processSpeechQueue() { + boolean speechQueueAvailable = false; + try { + speechQueueAvailable = speechQueueLock.tryLock(); + if (!speechQueueAvailable) { + return; + } + if (mSpeechQueue.size() < 1) { + mIsSpeaking = false; + // Dispatch a completion here as this is the + // only place where speech completes normally. + // Nothing left to say in the queue is a special case + // that is always a "mark" - associated text is null. + dispatchSpeechCompletedCallbacks(""); + return; + } + + SpeechItem currentSpeechItem = mSpeechQueue.get(0); + mIsSpeaking = true; + SoundResource sr = getSoundResource(currentSpeechItem); + // Synth speech as needed - synthesizer should call + // processSpeechQueue to continue running the queue + Log.i("TTS processing: ", currentSpeechItem.mText); + if (sr == null) { + if (currentSpeechItem.mType == SpeechItem.SPEECH) { + // TODO: Split text up into smaller chunks before accepting + // them + // for processing. + speakInternalOnly(currentSpeechItem.mText, + currentSpeechItem.mParams); + } else { + // This is either silence or an earcon that was missing + silence(currentSpeechItem.mDuration); + } + } else { + cleanUpPlayer(); + if (sr.mSourcePackageName == PKGNAME) { + // Utterance is part of the TTS library + mPlayer = MediaPlayer.create(this, sr.mResId); + } else if (sr.mSourcePackageName != null) { + // Utterance is part of the app calling the library + Context ctx; + try { + ctx = this.createPackageContext(sr.mSourcePackageName, + 0); + } catch (NameNotFoundException e) { + e.printStackTrace(); + mSpeechQueue.remove(0); // Remove it from the queue and + // move on + mIsSpeaking = false; + return; + } + mPlayer = MediaPlayer.create(ctx, sr.mResId); + } else { + // Utterance is coming from a file + mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename)); + } + + // Check if Media Server is dead; if it is, clear the queue and + // give up for now - hopefully, it will recover itself. + if (mPlayer == null) { + mSpeechQueue.clear(); + mIsSpeaking = false; + return; + } + mPlayer.setOnCompletionListener(this); + try { + mPlayer.start(); + } catch (IllegalStateException e) { + mSpeechQueue.clear(); + mIsSpeaking = false; + cleanUpPlayer(); + return; + } + } + if (mSpeechQueue.size() > 0) { + mSpeechQueue.remove(0); + } + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (speechQueueAvailable) { + speechQueueLock.unlock(); + } + } + } + + private void cleanUpPlayer() { + if (mPlayer != null) { + mPlayer.release(); + mPlayer = null; + } + } + + /** + * Synthesizes the given text using the specified queuing mode and + * parameters. + * + * @param text + * The String of text that should be synthesized + * @param params + * An ArrayList of parameters. The first element of this array + * controls the type of voice to use. + * @param filename + * The string that gives the full output filename; it should be + * something like "/sdcard/myappsounds/mysound.wav". + * @return A boolean that indicates if the synthesis succeeded + */ + private boolean synthesizeToFile(String text, ArrayList<String> params, + String filename, boolean calledFromApi) { + // Only stop everything if this is a call made by an outside app trying + // to + // use the API. Do NOT stop if this is a call from within the service as + // clearing the speech queue here would be a mistake. + if (calledFromApi) { + stop(); + } + Log.i("TTS", "Synthesizing to " + filename); + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + return false; + } + // Don't allow a filename that is too long + // TODO use platform constant + if (filename.length() > 250) { + return false; + } + nativeSynth.synthesizeToFile(text, filename); + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + Log.i("TTS", "Completed synthesis for " + filename); + return true; + } + + @Override + public IBinder onBind(Intent intent) { + if (ACTION.equals(intent.getAction())) { + for (String category : intent.getCategories()) { + if (category.equals(CATEGORY)) { + return mBinder; + } + } + } + return null; + } + + private final ITts.Stub mBinder = new Stub() { + + public void registerCallback(ITtsCallback cb) { + if (cb != null) + mCallbacks.register(cb); + } + + public void unregisterCallback(ITtsCallback cb) { + if (cb != null) + mCallbacks.unregister(cb); + } + + /** + * Gives a hint about the type of engine that is preferred. + * + * @param selectedEngine + * The TTS engine that should be used + */ + public void setEngine(String engineName, String[] supportedLanguages, + int strictness) { + mSelf.setEngine(engineName, supportedLanguages, strictness); + } + + /** + * Specifies exactly what the engine has to support. Will always be + * considered "strict"; can be used for implementing + * optional/experimental features that are not supported by all engines. + * + * @param engineIntent + * An intent that specifies exactly what the engine has to + * support. + */ + public void setEngineWithIntent(Intent engineIntent) { + mSelf.setEngine(engineIntent); + } + + /** + * Speaks the given text using the specified queueing mode and + * parameters. + * + * @param text + * The text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + */ + public void speak(String text, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.speak(text, queueMode, speakingParams); + } + + /** + * Plays the earcon using the specified queueing mode and parameters. + * + * @param earcon + * The earcon that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. + */ + public void playEarcon(String earcon, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.playEarcon(earcon, queueMode, speakingParams); + } + + /** + * Plays the silence using the specified queueing mode and parameters. + * + * @param duration + * The duration of the silence that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. + */ + public void playSilence(long duration, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.playSilence(duration, queueMode, speakingParams); + } + + + /** + * Stops all speech output and removes any utterances still in the + * queue. + */ + public void stop() { + mSelf.stop(); + } + + /** + * Returns whether or not the TTS is speaking. + * + * @return Boolean to indicate whether or not the TTS is speaking + */ + public boolean isSpeaking() { + return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1)); + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + public void addSpeech(String text, String packageName, int resId) { + mSelf.addSpeech(text, packageName, resId); + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a + * complete path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + public void addSpeechFile(String text, String filename) { + mSelf.addSpeech(text, filename); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + public void addEarcon(String earcon, String packageName, int resId) { + mSelf.addEarcon(earcon, packageName, resId); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a + * complete path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + public void addEarconFile(String earcon, String filename) { + mSelf.addEarcon(earcon, filename); + } + + /** + * Sets the speech rate for the TTS. Note that this will only have an + * effect on synthesized speech; it will not affect pre-recorded speech. + * + * @param speechRate + * The speech rate that should be used + */ + public void setSpeechRate(int speechRate) { + mSelf.setSpeechRate(speechRate); + } + + // TODO: Fix comment about language + /** + * Sets the speech rate for the TTS. Note that this will only have an + * effect on synthesized speech; it will not affect pre-recorded speech. + * + * @param language + * The language to be used. The languages are specified by + * their IETF language tags as defined by BCP 47. This is the + * same standard used for the lang attribute in HTML. See: + * http://en.wikipedia.org/wiki/IETF_language_tag + */ + public void setLanguage(String language) { + mSelf.setLanguage(language); + } + + /** + * Speaks the given text using the specified queueing mode and + * parameters. + * + * @param text + * The String of text that should be synthesized + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + * @param filename + * The string that gives the full output filename; it should + * be something like "/sdcard/myappsounds/mysound.wav". + * @return A boolean that indicates if the synthesis succeeded + */ + public boolean synthesizeToFile(String text, String[] params, + String filename) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + return mSelf.synthesizeToFile(text, speakingParams, filename, true); + } + }; + +} diff --git a/tts/jni/Android.mk b/tts/jni/Android.mk new file mode 100755 index 0000000..bb76583 --- /dev/null +++ b/tts/jni/Android.mk @@ -0,0 +1,34 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + android_tts_SynthProxy.cpp + +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) + +LOCAL_SHARED_LIBRARIES := \ + libandroid_runtime \ + libnativehelper \ + libmedia \ + libutils \ + libcutils + +ifneq ($(TARGET_SIMULATOR),true) +LOCAL_SHARED_LIBRARIES += \ + libdl +endif + +ifeq ($(TARGET_OS)-$(TARGET_SIMULATOR),linux-true) +LOCAL_LDLIBS += -ldl +endif + + +LOCAL_MODULE:= libttssynthproxy + +LOCAL_ARM_MODE := arm + +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) + diff --git a/tts/jni/android_tts_SynthProxy.cpp b/tts/jni/android_tts_SynthProxy.cpp new file mode 100755 index 0000000..d8f1bf3 --- /dev/null +++ b/tts/jni/android_tts_SynthProxy.cpp @@ -0,0 +1,595 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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 <stdio.h> +#include <unistd.h> + +#define LOG_TAG "SynthProxy" + +#include <utils/Log.h> +#include <nativehelper/jni.h> +#include <nativehelper/JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> +#include <tts/TtsEngine.h> +#include <media/AudioTrack.h> + +#include <dlfcn.h> + +#define DEFAULT_TTS_RATE 16000 +#define DEFAULT_TTS_FORMAT AudioSystem::PCM_16_BIT +#define DEFAULT_TTS_NB_CHANNELS 1 + +#define USAGEMODE_PLAY_IMMEDIATELY 0 +#define USAGEMODE_WRITE_TO_FILE 1 + +using namespace android; + +// ---------------------------------------------------------------------------- +struct fields_t { + jfieldID synthProxyFieldJniData; + jclass synthProxyClass; + jmethodID synthProxyMethodPost; +}; + +struct afterSynthData_t { + jint jniStorage; + int usageMode; + FILE* outputFile; +}; + +// ---------------------------------------------------------------------------- +static fields_t javaTTSFields; + +// ---------------------------------------------------------------------------- +class SynthProxyJniStorage { + public : + //jclass tts_class; + jobject tts_ref; + TtsEngine* mNativeSynthInterface; + AudioTrack* mAudioOut; + uint32_t mSampleRate; + AudioSystem::audio_format mAudFormat; + int mNbChannels; + + SynthProxyJniStorage() { + //tts_class = NULL; + tts_ref = NULL; + mNativeSynthInterface = NULL; + mAudioOut = NULL; + mSampleRate = DEFAULT_TTS_RATE; + mAudFormat = DEFAULT_TTS_FORMAT; + mNbChannels = DEFAULT_TTS_NB_CHANNELS; + } + + ~SynthProxyJniStorage() { + killAudio(); + if (mNativeSynthInterface) { + mNativeSynthInterface->shutdown(); + mNativeSynthInterface = NULL; + } + } + + void killAudio() { + if (mAudioOut) { + mAudioOut->stop(); + delete mAudioOut; + mAudioOut = NULL; + } + } + + void createAudioOut(uint32_t rate, AudioSystem::audio_format format, + int channel) { + mSampleRate = rate; + mAudFormat = format; + mNbChannels = channel; + + // TODO use the TTS stream type + int streamType = AudioSystem::MUSIC; + + // retrieve system properties to ensure successful creation of the + // AudioTrack object for playback + int afSampleRate; + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { + afSampleRate = 44100; + } + int afFrameCount; + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { + afFrameCount = 2048; + } + uint32_t afLatency; + if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) { + afLatency = 500; + } + uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate); + if (minBufCount < 2) minBufCount = 2; + int minFrameCount = (afFrameCount * rate * minBufCount)/afSampleRate; + + mAudioOut = new AudioTrack(streamType, rate, format, channel, + minFrameCount > 4096 ? minFrameCount : 4096, + 0, 0, 0, 0); // not using an AudioTrack callback + + if (mAudioOut->initCheck() != NO_ERROR) { + LOGI("AudioTrack error"); + delete mAudioOut; + mAudioOut = NULL; + } else { + LOGI("AudioTrack OK"); + mAudioOut->start(); + LOGI("AudioTrack started"); + } + } +}; + + +// ---------------------------------------------------------------------------- +void prepAudioTrack(SynthProxyJniStorage* pJniData, + uint32_t rate, AudioSystem::audio_format format, int channel) +{ + // Don't bother creating a new audiotrack object if the current + // object is already set. + if ( pJniData->mAudioOut && + (rate == pJniData->mSampleRate) && + (format == pJniData->mAudFormat) && + (channel == pJniData->mNbChannels) ){ + return; + } + if (pJniData->mAudioOut){ + pJniData->killAudio(); + } + pJniData->createAudioOut(rate, format, channel); +} + + +// ---------------------------------------------------------------------------- +/* + * Callback from TTS engine. + * Directly speaks using AudioTrack or write to file + */ +static void ttsSynthDoneCB(void * userdata, uint32_t rate, + AudioSystem::audio_format format, int channel, + int8_t *wav, size_t bufferSize) { + LOGI("ttsSynthDoneCallback: %d bytes", bufferSize); + + afterSynthData_t* pForAfter = (afterSynthData_t*)userdata; + + if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY){ + LOGI("Direct speech"); + + if (wav == NULL) { + LOGI("Null: speech has completed"); + } + + if (bufferSize > 0) { + SynthProxyJniStorage* pJniData = + (SynthProxyJniStorage*)(pForAfter->jniStorage); + prepAudioTrack(pJniData, rate, format, channel); + if (pJniData->mAudioOut) { + pJniData->mAudioOut->write(wav, bufferSize); + LOGI("AudioTrack wrote: %d bytes", bufferSize); + } else { + LOGI("Can't play, null audiotrack"); + } + } + } else if (pForAfter->usageMode == USAGEMODE_WRITE_TO_FILE) { + LOGI("Save to file"); + if (wav == NULL) { + LOGI("Null: speech has completed"); + } + if (bufferSize > 0){ + fwrite(wav, 1, bufferSize, pForAfter->outputFile); + } + } + // TODO update to call back into the SynthProxy class through the + // javaTTSFields.synthProxyMethodPost methode to notify + // playback has completed + + delete pForAfter; + return; +} + + +// ---------------------------------------------------------------------------- +static void +android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz, + jobject weak_this, jstring nativeSoLib) +{ + SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage(); + + prepAudioTrack(pJniStorage, + DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS); + + const char *nativeSoLibNativeString = + env->GetStringUTFChars(nativeSoLib, 0); + + void *engine_lib_handle = dlopen(nativeSoLibNativeString, + RTLD_NOW | RTLD_LOCAL); + if (engine_lib_handle==NULL) { + LOGI("engine_lib_handle==NULL"); + // TODO report error so the TTS can't be used + } else { + TtsEngine *(*get_TtsEngine)() = + reinterpret_cast<TtsEngine* (*)()>(dlsym(engine_lib_handle, "getTtsEngine")); + pJniStorage->mNativeSynthInterface = (*get_TtsEngine)(); + if (pJniStorage->mNativeSynthInterface) { + pJniStorage->mNativeSynthInterface->init(ttsSynthDoneCB); + } + } + + // we use a weak reference so the SynthProxy object can be garbage collected. + pJniStorage->tts_ref = env->NewGlobalRef(weak_this); + + // save the JNI resources so we can use them (and free them) later + env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, + (int)pJniStorage); + + env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString); +} + + +static void +android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData) { + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + delete pSynthData; + } +} + + +static void +android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData, + jstring language) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_setLanguage(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + const char *langNativeString = env->GetStringUTFChars(language, 0); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->setLanguage(langNativeString, + strlen(langNativeString)); + } + env->ReleaseStringUTFChars(language, langNativeString); +} + + +static void +android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData, + int speechRate) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_setSpeechRate(): invalid JNI data"); + return; + } + + int bufSize = 10; + char buffer [bufSize]; + sprintf(buffer, "%d", speechRate); + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + LOGI("setting speech rate to %d", speechRate); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->setProperty("rate", buffer, bufSize); + } +} + + +// TODO: Refactor this to get rid of any assumptions about sample rate, etc. +static void +android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, + jstring textJavaString, jstring filenameJavaString) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + + const char *filenameNativeString = + env->GetStringUTFChars(filenameJavaString, 0); + const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); + + afterSynthData_t* pForAfter = new (afterSynthData_t); + pForAfter->jniStorage = jniData; + pForAfter->usageMode = USAGEMODE_WRITE_TO_FILE; + + pForAfter->outputFile = fopen(filenameNativeString, "wb"); + + // Write 44 blank bytes for WAV header, then come back and fill them in + // after we've written the audio data + char header[44]; + fwrite(header, 1, 44, pForAfter->outputFile); + + unsigned int unique_identifier; + + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, + (void *)pForAfter); + } + + long filelen = ftell(pForAfter->outputFile); + + int samples = (((int)filelen) - 44) / 2; + header[0] = 'R'; + header[1] = 'I'; + header[2] = 'F'; + header[3] = 'F'; + ((uint32_t *)(&header[4]))[0] = filelen - 8; + header[8] = 'W'; + header[9] = 'A'; + header[10] = 'V'; + header[11] = 'E'; + + header[12] = 'f'; + header[13] = 'm'; + header[14] = 't'; + header[15] = ' '; + + ((uint32_t *)(&header[16]))[0] = 16; // size of fmt + + ((unsigned short *)(&header[20]))[0] = 1; // format + ((unsigned short *)(&header[22]))[0] = 1; // channels + ((uint32_t *)(&header[24]))[0] = 22050; // samplerate + ((uint32_t *)(&header[28]))[0] = 44100; // byterate + ((unsigned short *)(&header[32]))[0] = 2; // block align + ((unsigned short *)(&header[34]))[0] = 16; // bits per sample + + header[36] = 'd'; + header[37] = 'a'; + header[38] = 't'; + header[39] = 'a'; + + ((uint32_t *)(&header[40]))[0] = samples * 2; // size of data + + // Skip back to the beginning and rewrite the header + fseek(pForAfter->outputFile, 0, SEEK_SET); + fwrite(header, 1, 44, pForAfter->outputFile); + + fflush(pForAfter->outputFile); + fclose(pForAfter->outputFile); + + env->ReleaseStringUTFChars(textJavaString, textNativeString); + env->ReleaseStringUTFChars(filenameJavaString, filenameNativeString); +} + + +static void +android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, + jstring textJavaString) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_speak(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + + if (pSynthData->mAudioOut) { + pSynthData->mAudioOut->stop(); + pSynthData->mAudioOut->start(); + } + + afterSynthData_t* pForAfter = new (afterSynthData_t); + pForAfter->jniStorage = jniData; + pForAfter->usageMode = USAGEMODE_PLAY_IMMEDIATELY; + + if (pSynthData->mNativeSynthInterface) { + const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); + pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, + (void *)pForAfter); + env->ReleaseStringUTFChars(textJavaString, textNativeString); + } +} + + +static void +android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_stop(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->stop(); + } + if (pSynthData->mAudioOut) { + pSynthData->mAudioOut->stop(); + } +} + + +static void +android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_shutdown(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->shutdown(); + pSynthData->mNativeSynthInterface = NULL; + } +} + + +// TODO add buffer format +static void +android_tts_SynthProxy_playAudioBuffer(JNIEnv *env, jobject thiz, jint jniData, + int bufferPointer, int bufferSize) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_playAudioBuffer(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + short* wav = (short*) bufferPointer; + pSynthData->mAudioOut->write(wav, bufferSize); + LOGI("AudioTrack wrote: %d bytes", bufferSize); +} + + +JNIEXPORT jstring JNICALL +android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data"); + return env->NewStringUTF(""); + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + size_t bufSize = 100; + char buf[bufSize]; + memset(buf, 0, bufSize); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->getLanguage(buf, &bufSize); + } + return env->NewStringUTF(buf); +} + +JNIEXPORT int JNICALL +android_tts_SynthProxy_getRate(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_getRate(): invalid JNI data"); + return 0; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + size_t bufSize = 100; + + char buf[bufSize]; + memset(buf, 0, bufSize); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->getProperty("rate", buf, &bufSize); + } + return atoi(buf); +} + +// Dalvik VM type signatures +static JNINativeMethod gMethods[] = { + { "native_stop", + "(I)V", + (void*)android_tts_SynthProxy_stop + }, + { "native_speak", + "(ILjava/lang/String;)V", + (void*)android_tts_SynthProxy_speak + }, + { "native_synthesizeToFile", + "(ILjava/lang/String;Ljava/lang/String;)V", + (void*)android_tts_SynthProxy_synthesizeToFile + }, + { "native_setLanguage", + "(ILjava/lang/String;)V", + (void*)android_tts_SynthProxy_setLanguage + }, + { "native_setSpeechRate", + "(II)V", + (void*)android_tts_SynthProxy_setSpeechRate + }, + { "native_playAudioBuffer", + "(III)V", + (void*)android_tts_SynthProxy_playAudioBuffer + }, + { "native_getLanguage", + "(I)Ljava/lang/String;", + (void*)android_tts_SynthProxy_getLanguage + }, + { "native_getRate", + "(I)I", + (void*)android_tts_SynthProxy_getRate + }, + { "native_shutdown", + "(I)V", + (void*)android_tts_SynthProxy_shutdown + }, + { "native_setup", + "(Ljava/lang/Object;Ljava/lang/String;)V", + (void*)android_tts_SynthProxy_native_setup + }, + { "native_finalize", + "(I)V", + (void*)android_tts_SynthProxy_native_finalize + } +}; + +#define SP_JNIDATA_FIELD_NAME "mJniData" +#define SP_POSTSPEECHSYNTHESIZED_METHOD_NAME "postNativeSpeechSynthesizedInJava" + +// TODO: verify this is the correct path +static const char* const kClassPathName = "android/tts/SynthProxy"; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + jint result = -1; + jclass clazz; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("ERROR: GetEnv failed\n"); + goto bail; + } + assert(env != NULL); + + clazz = env->FindClass(kClassPathName); + if (clazz == NULL) { + LOGE("Can't find %s", kClassPathName); + goto bail; + } + + javaTTSFields.synthProxyClass = clazz; + javaTTSFields.synthProxyFieldJniData = NULL; + javaTTSFields.synthProxyMethodPost = NULL; + + javaTTSFields.synthProxyFieldJniData = env->GetFieldID(clazz, + SP_JNIDATA_FIELD_NAME, "I"); + if (javaTTSFields.synthProxyFieldJniData == NULL) { + LOGE("Can't find %s.%s field", kClassPathName, SP_JNIDATA_FIELD_NAME); + goto bail; + } + + javaTTSFields.synthProxyMethodPost = env->GetStaticMethodID(clazz, + SP_POSTSPEECHSYNTHESIZED_METHOD_NAME, "(Ljava/lang/Object;II)V"); + if (javaTTSFields.synthProxyMethodPost == NULL) { + LOGE("Can't find %s.%s method", kClassPathName, SP_POSTSPEECHSYNTHESIZED_METHOD_NAME); + goto bail; + } + + if (jniRegisterNativeMethods( + env, kClassPathName, gMethods, NELEM(gMethods)) < 0) + goto bail; + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + + bail: + return result; +} |
