diff options
56 files changed, 4152 insertions, 1073 deletions
diff --git a/api/current.txt b/api/current.txt index 6056bf785f56..0e3fee64c667 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2688,13 +2688,20 @@ package android.app { method public final deprecated boolean showDialog(int, android.os.Bundle); method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback); method public void startActivityForResult(android.content.Intent, int); + method public void startActivityForResult(android.content.Intent, int, android.os.Bundle); method public void startActivityFromChild(android.app.Activity, android.content.Intent, int); + method public void startActivityFromChild(android.app.Activity, android.content.Intent, int, android.os.Bundle); method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int); + method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle); method public boolean startActivityIfNeeded(android.content.Intent, int); + method public boolean startActivityIfNeeded(android.content.Intent, int, android.os.Bundle); method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; + method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; + method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public deprecated void startManagingCursor(android.database.Cursor); method public boolean startNextMatchingActivity(android.content.Intent); + method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle); method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean); method public deprecated void stopManagingCursor(android.database.Cursor); method public void takeKeyEvents(boolean); @@ -3320,7 +3327,9 @@ package android.app { method public void setTargetFragment(android.app.Fragment, int); method public void setUserVisibleHint(boolean); method public void startActivity(android.content.Intent); + method public void startActivity(android.content.Intent, android.os.Bundle); method public void startActivityForResult(android.content.Intent, int); + method public void startActivityForResult(android.content.Intent, int, android.os.Bundle); method public void unregisterForContextMenu(android.view.View); } @@ -5149,9 +5158,12 @@ package android.content { method public abstract deprecated void setWallpaper(android.graphics.Bitmap) throws java.io.IOException; method public abstract deprecated void setWallpaper(java.io.InputStream) throws java.io.IOException; method public abstract void startActivities(android.content.Intent[]); + method public abstract void startActivities(android.content.Intent[], android.os.Bundle); method public abstract void startActivity(android.content.Intent); + method public abstract void startActivity(android.content.Intent, android.os.Bundle); method public abstract boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle); method public abstract void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; + method public abstract void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public abstract android.content.ComponentName startService(android.content.Intent); method public abstract boolean stopService(android.content.Intent); method public abstract void unbindService(android.content.ServiceConnection); @@ -5274,9 +5286,12 @@ package android.content { method public void setWallpaper(android.graphics.Bitmap) throws java.io.IOException; method public void setWallpaper(java.io.InputStream) throws java.io.IOException; method public void startActivities(android.content.Intent[]); + method public void startActivities(android.content.Intent[], android.os.Bundle); method public void startActivity(android.content.Intent); + method public void startActivity(android.content.Intent, android.os.Bundle); method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle); method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; + method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public android.content.ComponentName startService(android.content.Intent); method public boolean stopService(android.content.Intent); method public void unbindService(android.content.ServiceConnection); @@ -19926,9 +19941,12 @@ package android.test.mock { method public void setWallpaper(android.graphics.Bitmap) throws java.io.IOException; method public void setWallpaper(java.io.InputStream) throws java.io.IOException; method public void startActivities(android.content.Intent[]); + method public void startActivities(android.content.Intent[], android.os.Bundle); method public void startActivity(android.content.Intent); + method public void startActivity(android.content.Intent, android.os.Bundle); method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle); method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException; + method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException; method public android.content.ComponentName startService(android.content.Intent); method public boolean stopService(android.content.Intent); method public void unbindService(android.content.ServiceConnection); diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index da38df13ae81..c15c49f9d086 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -57,16 +57,13 @@ public class Am { private int mNextArg; private String mCurArgData; - private boolean mDebugOption = false; + private int mStartFlags = 0; private boolean mWaitOption = false; private boolean mStopOption = false; - private boolean mOpenglTraceOption = false; - private int mRepeat = 0; private String mProfileFile; - private boolean mProfileAutoStop; // These are magic strings understood by the Eclipse plugin. private static final String FATAL_ERROR_CODE = "Error type 1"; @@ -150,10 +147,9 @@ public class Am { Intent baseIntent = intent; boolean hasIntentInfo = false; - mDebugOption = false; + mStartFlags = 0; mWaitOption = false; mStopOption = false; - mOpenglTraceOption = false; mRepeat = 0; mProfileFile = null; Uri data = null; @@ -297,21 +293,21 @@ public class Am { intent.setDataAndType(data, type); intent = new Intent(); } else if (opt.equals("-D")) { - mDebugOption = true; + mStartFlags |= ActivityManager.START_FLAG_DEBUG; } else if (opt.equals("-W")) { mWaitOption = true; } else if (opt.equals("-P")) { mProfileFile = nextArgRequired(); - mProfileAutoStop = true; + mStartFlags |= ActivityManager.START_FLAG_AUTO_STOP_PROFILER; } else if (opt.equals("--start-profiler")) { mProfileFile = nextArgRequired(); - mProfileAutoStop = false; + mStartFlags &= ~ActivityManager.START_FLAG_AUTO_STOP_PROFILER; } else if (opt.equals("-R")) { mRepeat = Integer.parseInt(nextArgRequired()); } else if (opt.equals("-S")) { mStopOption = true; } else if (opt.equals("--opengl-trace")) { - mOpenglTraceOption = true; + mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES; } else { System.err.println("Error: Unknown option: " + opt); showUsage(); @@ -450,64 +446,60 @@ public class Am { int res; if (mWaitOption) { result = mAm.startActivityAndWait(null, intent, mimeType, - null, 0, null, null, 0, false, - mDebugOption, mOpenglTraceOption, - mProfileFile, fd, mProfileAutoStop); + null, null, 0, mStartFlags, mProfileFile, fd, null); res = result.result; } else { res = mAm.startActivity(null, intent, mimeType, - null, 0, null, null, 0, false, - mDebugOption, mOpenglTraceOption, - mProfileFile, fd, mProfileAutoStop); + null, null, 0, mStartFlags, mProfileFile, fd, null); } PrintStream out = mWaitOption ? System.out : System.err; boolean launched = false; switch (res) { - case IActivityManager.START_SUCCESS: + case ActivityManager.START_SUCCESS: launched = true; break; - case IActivityManager.START_SWITCHES_CANCELED: + case ActivityManager.START_SWITCHES_CANCELED: launched = true; out.println( "Warning: Activity not started because the " + " current activity is being kept for the user."); break; - case IActivityManager.START_DELIVERED_TO_TOP: + case ActivityManager.START_DELIVERED_TO_TOP: launched = true; out.println( "Warning: Activity not started, intent has " + "been delivered to currently running " + "top-most instance."); break; - case IActivityManager.START_RETURN_INTENT_TO_CALLER: + case ActivityManager.START_RETURN_INTENT_TO_CALLER: launched = true; out.println( "Warning: Activity not started because intent " + "should be handled by the caller"); break; - case IActivityManager.START_TASK_TO_FRONT: + case ActivityManager.START_TASK_TO_FRONT: launched = true; out.println( "Warning: Activity not started, its current " + "task has been brought to the front"); break; - case IActivityManager.START_INTENT_NOT_RESOLVED: + case ActivityManager.START_INTENT_NOT_RESOLVED: out.println( "Error: Activity not started, unable to " + "resolve " + intent.toString()); break; - case IActivityManager.START_CLASS_NOT_FOUND: + case ActivityManager.START_CLASS_NOT_FOUND: out.println(NO_CLASS_ERROR_CODE); out.println("Error: Activity class " + intent.getComponent().toShortString() + " does not exist."); break; - case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: + case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: out.println( "Error: Activity not started, you requested to " + "both forward and receive its result"); break; - case IActivityManager.START_PERMISSION_DENIED: + case ActivityManager.START_PERMISSION_DENIED: out.println( "Error: Activity not started, you do not " + "have permission to access it."); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 0f2aa4608f6f..dc12acd51c0f 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3166,42 +3166,60 @@ public class Activity extends ContextThemeWrapper } /** + * Same as calling {@link #startActivityForResult(Intent, int, Bundle)} + * with no options. + * + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivity + */ + public void startActivityForResult(Intent intent, int requestCode) { + startActivityForResult(intent, requestCode, null); + } + + /** * Launch an activity for which you would like a result when it finished. * When this activity exits, your * onActivityResult() method will be called with the given requestCode. * Using a negative requestCode is the same as calling * {@link #startActivity} (the activity is not launched as a sub-activity). - * + * * <p>Note that this method should only be used with Intent protocols * that are defined to return a result. In other protocols (such as * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may * not get the result when you expect. For example, if the activity you * are launching uses the singleTask launch mode, it will not run in your * task and thus you will immediately receive a cancel result. - * + * * <p>As a special case, if you call startActivityForResult() with a requestCode * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your * activity, then your window will not be displayed until a result is * returned back from the started activity. This is to avoid visible * flickering when redirecting to another activity. - * + * * <p>This method throws {@link android.content.ActivityNotFoundException} * if there was no Activity found to run the given Intent. - * + * * @param intent The intent to start. * @param requestCode If >= 0, this code will be returned in * onActivityResult() when the activity exits. - * + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. + * * @throws android.content.ActivityNotFoundException - * + * * @see #startActivity */ - public void startActivityForResult(Intent intent, int requestCode) { + public void startActivityForResult(Intent intent, int requestCode, Bundle options) { if (mParent == null) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, - intent, requestCode); + intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), @@ -3218,11 +3236,39 @@ public class Activity extends ContextThemeWrapper mStartedActivity = true; } } else { - mParent.startActivityFromChild(this, intent, requestCode); + if (options != null) { + mParent.startActivityFromChild(this, intent, requestCode, options); + } else { + // Note we want to go through this method for compatibility with + // existing applications that may have overridden it. + mParent.startActivityFromChild(this, intent, requestCode); + } } } /** + * Same as calling {@link #startIntentSenderForResult(IntentSender, int, + * Intent, int, int, int, Bundle)} with no options. + * + * @param intent The IntentSender to launch. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * <var>flagsMask</var> + * @param extraFlags Always set to 0. + */ + public void startIntentSenderForResult(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, + flagsValues, extraFlags, null); + } + + /** * Like {@link #startActivityForResult(Intent, int)}, but allowing you * to use a IntentSender to describe the activity to be started. If * the IntentSender is for an activity, that activity will be started @@ -3241,21 +3287,29 @@ public class Activity extends ContextThemeWrapper * @param flagsValues Desired values for any bits set in * <var>flagsMask</var> * @param extraFlags Always set to 0. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. */ public void startIntentSenderForResult(IntentSender intent, int requestCode, - Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) - throws IntentSender.SendIntentException { + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException { if (mParent == null) { startIntentSenderForResultInner(intent, requestCode, fillInIntent, - flagsMask, flagsValues, this); + flagsMask, flagsValues, this, options); + } else if (options != null) { + mParent.startIntentSenderFromChild(this, intent, requestCode, + fillInIntent, flagsMask, flagsValues, extraFlags, options); } else { + // Note we want to go through this call for compatibility with + // existing applications that may have overridden the method. mParent.startIntentSenderFromChild(this, intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags); } } private void startIntentSenderForResultInner(IntentSender intent, int requestCode, - Intent fillInIntent, int flagsMask, int flagsValues, Activity activity) + Intent fillInIntent, int flagsMask, int flagsValues, Activity activity, + Bundle options) throws IntentSender.SendIntentException { try { String resolvedType = null; @@ -3266,8 +3320,8 @@ public class Activity extends ContextThemeWrapper int result = ActivityManagerNative.getDefault() .startActivityIntentSender(mMainThread.getApplicationThread(), intent, fillInIntent, resolvedType, mToken, activity.mEmbeddedID, - requestCode, flagsMask, flagsValues); - if (result == IActivityManager.START_CANCELED) { + requestCode, flagsMask, flagsValues, options); + if (result == ActivityManager.START_CANCELED) { throw new IntentSender.SendIntentException(); } Instrumentation.checkStartActivityResult(result, null); @@ -3286,6 +3340,22 @@ public class Activity extends ContextThemeWrapper } /** + * Same as {@link #startActivity(Intent, Bundle)} with no options + * specified. + * + * @param intent The intent to start. + * + * @throws android.content.ActivityNotFoundException + * + * @see {@link #startActivity(Intent, Bundle)} + * @see #startActivityForResult + */ + @Override + public void startActivity(Intent intent) { + startActivity(intent, null); + } + + /** * Launch a new activity. You will not receive any information about when * the activity exits. This implementation overrides the base version, * providing information about @@ -3298,14 +3368,39 @@ public class Activity extends ContextThemeWrapper * if there was no Activity found to run the given Intent. * * @param intent The intent to start. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. * * @throws android.content.ActivityNotFoundException - * + * + * @see {@link #startActivity(Intent)} * @see #startActivityForResult */ @Override - public void startActivity(Intent intent) { - startActivityForResult(intent, -1); + public void startActivity(Intent intent, Bundle options) { + if (options != null) { + startActivityForResult(intent, -1, options); + } else { + // Note we want to go through this call for compatibility with + // applications that may have overridden the method. + startActivityForResult(intent, -1); + } + } + + /** + * Same as {@link #startActivities(Intent[], Bundle)} with no options + * specified. + * + * @param intents The intents to start. + * + * @throws android.content.ActivityNotFoundException + * + * @see {@link #startActivities(Intent[], Bundle)} + * @see #startActivityForResult + */ + @Override + public void startActivities(Intent[] intents) { + startActivities(intents, null); } /** @@ -3321,22 +3416,23 @@ public class Activity extends ContextThemeWrapper * if there was no Activity found to run the given Intent. * * @param intents The intents to start. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. * * @throws android.content.ActivityNotFoundException * + * @see {@link #startActivities(Intent[])} * @see #startActivityForResult */ @Override - public void startActivities(Intent[] intents) { + public void startActivities(Intent[] intents, Bundle options) { mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(), - mToken, this, intents); + mToken, this, intents, options); } /** - * Like {@link #startActivity(Intent)}, but taking a IntentSender - * to start; see - * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} - * for more information. + * Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)} + * with no options. * * @param intent The IntentSender to launch. * @param fillInIntent If non-null, this will be provided as the @@ -3350,8 +3446,58 @@ public class Activity extends ContextThemeWrapper public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { - startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, - flagsValues, extraFlags); + startIntentSender(intent, fillInIntent, flagsMask, flagsValues, + extraFlags, null); + } + + /** + * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender + * to start; see + * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle)} + * for more information. + * + * @param intent The IntentSender to launch. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * <var>flagsMask</var> + * @param extraFlags Always set to 0. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. + */ + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException { + if (options != null) { + startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, + flagsValues, extraFlags, options); + } else { + // Note we want to go through this call for compatibility with + // applications that may have overridden the method. + startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, + flagsValues, extraFlags); + } + } + + /** + * Same as calling {@link #startActivityIfNeeded(Intent, int, Bundle)} + * with no options. + * + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits, as described in + * {@link #startActivityForResult}. + * + * @return If a new activity was launched then true is returned; otherwise + * false is returned and you must handle the Intent yourself. + * + * @see #startActivity + * @see #startActivityForResult + */ + public boolean startActivityIfNeeded(Intent intent, int requestCode) { + return startActivityIfNeeded(intent, requestCode, null); } /** @@ -3374,6 +3520,8 @@ public class Activity extends ContextThemeWrapper * @param requestCode If >= 0, this code will be returned in * onActivityResult() when the activity exits, as described in * {@link #startActivityForResult}. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. * * @return If a new activity was launched then true is returned; otherwise * false is returned and you must handle the Intent yourself. @@ -3381,17 +3529,17 @@ public class Activity extends ContextThemeWrapper * @see #startActivity * @see #startActivityForResult */ - public boolean startActivityIfNeeded(Intent intent, int requestCode) { + public boolean startActivityIfNeeded(Intent intent, int requestCode, Bundle options) { if (mParent == null) { - int result = IActivityManager.START_RETURN_INTENT_TO_CALLER; + int result = ActivityManager.START_RETURN_INTENT_TO_CALLER; try { intent.setAllowFds(false); result = ActivityManagerNative.getDefault() .startActivity(mMainThread.getApplicationThread(), intent, intent.resolveTypeIfNeeded(getContentResolver()), - null, 0, - mToken, mEmbeddedID, requestCode, true /* onlyIfNeeded */, - false, false, null, null, false); + mToken, mEmbeddedID, requestCode, + ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, null, + options); } catch (RemoteException e) { // Empty } @@ -3408,7 +3556,7 @@ public class Activity extends ContextThemeWrapper // activity is finished, no matter what happens to it. mStartedActivity = true; } - return result != IActivityManager.START_RETURN_INTENT_TO_CALLER; + return result != ActivityManager.START_RETURN_INTENT_TO_CALLER; } throw new UnsupportedOperationException( @@ -3416,6 +3564,24 @@ public class Activity extends ContextThemeWrapper } /** + * Same as calling {@link #startNextMatchingActivity(Intent, Bundle)} with + * no options. + * + * @param intent The intent to dispatch to the next activity. For + * correct behavior, this must be the same as the Intent that started + * your own activity; the only changes you can make are to the extras + * inside of it. + * + * @return Returns a boolean indicating whether there was another Activity + * to start: true if there was a next activity to start, false if there + * wasn't. In general, if true is returned you will then want to call + * finish() on yourself. + */ + public boolean startNextMatchingActivity(Intent intent) { + return startNextMatchingActivity(intent, null); + } + + /** * Special version of starting an activity, for use when you are replacing * other activity components. You can use this to hand the Intent off * to the next Activity that can handle it. You typically call this in @@ -3425,18 +3591,20 @@ public class Activity extends ContextThemeWrapper * correct behavior, this must be the same as the Intent that started * your own activity; the only changes you can make are to the extras * inside of it. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. * * @return Returns a boolean indicating whether there was another Activity * to start: true if there was a next activity to start, false if there * wasn't. In general, if true is returned you will then want to call * finish() on yourself. */ - public boolean startNextMatchingActivity(Intent intent) { + public boolean startNextMatchingActivity(Intent intent, Bundle options) { if (mParent == null) { try { intent.setAllowFds(false); return ActivityManagerNative.getDefault() - .startNextMatchingActivity(mToken, intent); + .startNextMatchingActivity(mToken, intent, options); } catch (RemoteException e) { // Empty } @@ -3446,7 +3614,25 @@ public class Activity extends ContextThemeWrapper throw new UnsupportedOperationException( "startNextMatchingActivity can only be called from a top-level activity"); } - + + /** + * Same as calling {@link #startActivityFromChild(Activity, Intent, int, Bundle)} + * with no options. + * + * @param child The activity making the call. + * @param intent The intent to start. + * @param requestCode Reply request code. < 0 if reply is not requested. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivity + * @see #startActivityForResult + */ + public void startActivityFromChild(Activity child, Intent intent, + int requestCode) { + startActivityFromChild(child, intent, requestCode); + } + /** * This is called when a child activity of this one calls its * {@link #startActivity} or {@link #startActivityForResult} method. @@ -3456,7 +3642,9 @@ public class Activity extends ContextThemeWrapper * * @param child The activity making the call. * @param intent The intent to start. - * @param requestCode Reply request code. < 0 if reply is not requested. + * @param requestCode Reply request code. < 0 if reply is not requested. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. * * @throws android.content.ActivityNotFoundException * @@ -3464,11 +3652,11 @@ public class Activity extends ContextThemeWrapper * @see #startActivityForResult */ public void startActivityFromChild(Activity child, Intent intent, - int requestCode) { + int requestCode, Bundle options) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, child, - intent, requestCode); + intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, child.mEmbeddedID, requestCode, @@ -3477,6 +3665,24 @@ public class Activity extends ContextThemeWrapper } /** + * Same as calling {@link #startActivityFromFragment(Fragment, Intent, int, Bundle)} + * with no options. + * + * @param fragment The fragment making the call. + * @param intent The intent to start. + * @param requestCode Reply request code. < 0 if reply is not requested. + * + * @throws android.content.ActivityNotFoundException + * + * @see Fragment#startActivity + * @see Fragment#startActivityForResult + */ + public void startActivityFromFragment(Fragment fragment, Intent intent, + int requestCode) { + startActivityFromFragment(fragment, intent, requestCode, null); + } + + /** * This is called when a Fragment in this activity calls its * {@link Fragment#startActivity} or {@link Fragment#startActivityForResult} * method. @@ -3487,6 +3693,8 @@ public class Activity extends ContextThemeWrapper * @param fragment The fragment making the call. * @param intent The intent to start. * @param requestCode Reply request code. < 0 if reply is not requested. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. * * @throws android.content.ActivityNotFoundException * @@ -3494,11 +3702,11 @@ public class Activity extends ContextThemeWrapper * @see Fragment#startActivityForResult */ public void startActivityFromFragment(Fragment fragment, Intent intent, - int requestCode) { + int requestCode, Bundle options) { Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, fragment, - intent, requestCode); + intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, fragment.mWho, requestCode, @@ -3507,6 +3715,18 @@ public class Activity extends ContextThemeWrapper } /** + * Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender, + * int, Intent, int, int, int, Bundle)} with no options. + */ + public void startIntentSenderFromChild(Activity child, IntentSender intent, + int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, + int extraFlags) + throws IntentSender.SendIntentException { + startIntentSenderFromChild(child, intent, requestCode, fillInIntent, + flagsMask, flagsValues, extraFlags, null); + } + + /** * Like {@link #startActivityFromChild(Activity, Intent, int)}, but * taking a IntentSender; see * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} @@ -3514,10 +3734,10 @@ public class Activity extends ContextThemeWrapper */ public void startIntentSenderFromChild(Activity child, IntentSender intent, int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, - int extraFlags) + int extraFlags, Bundle options) throws IntentSender.SendIntentException { startIntentSenderForResultInner(intent, requestCode, fillInIntent, - flagsMask, flagsValues, child); + flagsMask, flagsValues, child, options); } /** @@ -3843,7 +4063,7 @@ public class Activity extends ContextThemeWrapper data.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( - IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, + ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, mParent == null ? mToken : mParent.mToken, mEmbeddedID, requestCode, new Intent[] { data }, null, flags); return target != null ? new PendingIntent(target) : null; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 59c803e0a5c8..d056b171d453 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -59,6 +59,154 @@ public class ActivityManager { private final Context mContext; private final Handler mHandler; + /** + * Result for IActivityManager.startActivity: an error where the + * start had to be canceled. + * @hide + */ + public static final int START_CANCELED = -6; + + /** + * Result for IActivityManager.startActivity: an error where the + * thing being started is not an activity. + * @hide + */ + public static final int START_NOT_ACTIVITY = -5; + + /** + * Result for IActivityManager.startActivity: an error where the + * caller does not have permission to start the activity. + * @hide + */ + public static final int START_PERMISSION_DENIED = -4; + + /** + * Result for IActivityManager.startActivity: an error where the + * caller has requested both to forward a result and to receive + * a result. + * @hide + */ + public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3; + + /** + * Result for IActivityManager.startActivity: an error where the + * requested class is not found. + * @hide + */ + public static final int START_CLASS_NOT_FOUND = -2; + + /** + * Result for IActivityManager.startActivity: an error where the + * given Intent could not be resolved to an activity. + * @hide + */ + public static final int START_INTENT_NOT_RESOLVED = -1; + + /** + * Result for IActivityManaqer.startActivity: the activity was started + * successfully as normal. + * @hide + */ + public static final int START_SUCCESS = 0; + + /** + * Result for IActivityManaqer.startActivity: the caller asked that the Intent not + * be executed if it is the recipient, and that is indeed the case. + * @hide + */ + public static final int START_RETURN_INTENT_TO_CALLER = 1; + + /** + * Result for IActivityManaqer.startActivity: activity wasn't really started, but + * a task was simply brought to the foreground. + * @hide + */ + public static final int START_TASK_TO_FRONT = 2; + + /** + * Result for IActivityManaqer.startActivity: activity wasn't really started, but + * the given Intent was given to the existing top activity. + * @hide + */ + public static final int START_DELIVERED_TO_TOP = 3; + + /** + * Result for IActivityManaqer.startActivity: request was canceled because + * app switches are temporarily canceled to ensure the user's last request + * (such as pressing home) is performed. + * @hide + */ + public static final int START_SWITCHES_CANCELED = 4; + + /** + * Flag for IActivityManaqer.startActivity: do special start mode where + * a new activity is launched only if it is needed. + * @hide + */ + public static final int START_FLAG_ONLY_IF_NEEDED = 1<<0; + + /** + * Flag for IActivityManaqer.startActivity: launch the app for + * debugging. + * @hide + */ + public static final int START_FLAG_DEBUG = 1<<1; + + /** + * Flag for IActivityManaqer.startActivity: launch the app for + * OpenGL tracing. + * @hide + */ + public static final int START_FLAG_OPENGL_TRACES = 1<<2; + + /** + * Flag for IActivityManaqer.startActivity: if the app is being + * launched for profiling, automatically stop the profiler once done. + * @hide + */ + public static final int START_FLAG_AUTO_STOP_PROFILER = 1<<3; + + /** + * Result for IActivityManaqer.broadcastIntent: success! + * @hide + */ + public static final int BROADCAST_SUCCESS = 0; + + /** + * Result for IActivityManaqer.broadcastIntent: attempt to broadcast + * a sticky intent without appropriate permission. + * @hide + */ + public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1; + + /** + * Type for IActivityManaqer.getIntentSender: this PendingIntent is + * for a sendBroadcast operation. + * @hide + */ + public static final int INTENT_SENDER_BROADCAST = 1; + + /** + * Type for IActivityManaqer.getIntentSender: this PendingIntent is + * for a startActivity operation. + * @hide + */ + public static final int INTENT_SENDER_ACTIVITY = 2; + + /** + * Type for IActivityManaqer.getIntentSender: this PendingIntent is + * for an activity result operation. + * @hide + */ + public static final int INTENT_SENDER_ACTIVITY_RESULT = 3; + + /** + * Type for IActivityManaqer.getIntentSender: this PendingIntent is + * for a startService operation. + * @hide + */ + public static final int INTENT_SENDER_SERVICE = 4; + /*package*/ ActivityManager(Context context, Handler handler) { mContext = context; mHandler = handler; diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 7daaf7da88e9..732d211233ea 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -117,22 +117,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IApplicationThread app = ApplicationThreadNative.asInterface(b); Intent intent = Intent.CREATOR.createFromParcel(data); String resolvedType = data.readString(); - Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR); - int grantedMode = data.readInt(); IBinder resultTo = data.readStrongBinder(); String resultWho = data.readString(); int requestCode = data.readInt(); - boolean onlyIfNeeded = data.readInt() != 0; - boolean debug = data.readInt() != 0; - boolean openglTrace = data.readInt() != 0; + int startFlags = data.readInt(); String profileFile = data.readString(); ParcelFileDescriptor profileFd = data.readInt() != 0 ? data.readFileDescriptor() : null; - boolean autoStopProfiler = data.readInt() != 0; + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; int result = startActivity(app, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, openglTrace, - profileFile, profileFd, autoStopProfiler); + resultTo, resultWho, requestCode, startFlags, + profileFile, profileFd, options); reply.writeNoException(); reply.writeInt(result); return true; @@ -145,22 +141,18 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IApplicationThread app = ApplicationThreadNative.asInterface(b); Intent intent = Intent.CREATOR.createFromParcel(data); String resolvedType = data.readString(); - Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR); - int grantedMode = data.readInt(); IBinder resultTo = data.readStrongBinder(); String resultWho = data.readString(); int requestCode = data.readInt(); - boolean onlyIfNeeded = data.readInt() != 0; - boolean debug = data.readInt() != 0; - boolean openglTrace = data.readInt() != 0; + int startFlags = data.readInt(); String profileFile = data.readString(); ParcelFileDescriptor profileFd = data.readInt() != 0 ? data.readFileDescriptor() : null; - boolean autoStopProfiler = data.readInt() != 0; + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; WaitResult result = startActivityAndWait(app, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, openglTrace, - profileFile, profileFd, autoStopProfiler); + resultTo, resultWho, requestCode, startFlags, + profileFile, profileFd, options); reply.writeNoException(); result.writeToParcel(reply, 0); return true; @@ -173,17 +165,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IApplicationThread app = ApplicationThreadNative.asInterface(b); Intent intent = Intent.CREATOR.createFromParcel(data); String resolvedType = data.readString(); - Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR); - int grantedMode = data.readInt(); IBinder resultTo = data.readStrongBinder(); String resultWho = data.readString(); int requestCode = data.readInt(); - boolean onlyIfNeeded = data.readInt() != 0; - boolean debug = data.readInt() != 0; + int startFlags = data.readInt(); Configuration config = Configuration.CREATOR.createFromParcel(data); + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; int result = startActivityWithConfig(app, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, config); + resultTo, resultWho, requestCode, startFlags, config, options); reply.writeNoException(); reply.writeInt(result); return true; @@ -205,9 +195,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM int requestCode = data.readInt(); int flagsMask = data.readInt(); int flagsValues = data.readInt(); + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; int result = startActivityIntentSender(app, intent, fillInIntent, resolvedType, resultTo, resultWho, - requestCode, flagsMask, flagsValues); + requestCode, flagsMask, flagsValues, options); reply.writeNoException(); reply.writeInt(result); return true; @@ -218,7 +210,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); IBinder callingActivity = data.readStrongBinder(); Intent intent = Intent.CREATOR.createFromParcel(data); - boolean result = startNextMatchingActivity(callingActivity, intent); + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; + boolean result = startNextMatchingActivity(callingActivity, intent, options); reply.writeNoException(); reply.writeInt(result ? 1 : 0); return true; @@ -1231,9 +1225,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IBinder resultTo = data.readStrongBinder(); String resultWho = data.readString(); int requestCode = data.readInt(); - boolean onlyIfNeeded = data.readInt() != 0; + int startFlags = data.readInt(); + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; int result = startActivityInPackage(uid, intent, resolvedType, - resultTo, resultWho, requestCode, onlyIfNeeded); + resultTo, resultWho, requestCode, startFlags, options); reply.writeNoException(); reply.writeInt(result); return true; @@ -1412,7 +1408,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM Intent[] intents = data.createTypedArray(Intent.CREATOR); String[] resolvedTypes = data.createStringArray(); IBinder resultTo = data.readStrongBinder(); - int result = startActivitiesInPackage(uid, intents, resolvedTypes, resultTo); + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; + int result = startActivitiesInPackage(uid, intents, resolvedTypes, + resultTo, options); reply.writeNoException(); reply.writeInt(result); return true; @@ -1426,7 +1425,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM Intent[] intents = data.createTypedArray(Intent.CREATOR); String[] resolvedTypes = data.createStringArray(); IBinder resultTo = data.readStrongBinder(); - int result = startActivities(app, intents, resolvedTypes, resultTo); + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; + int result = startActivities(app, intents, resolvedTypes, resultTo, + options); reply.writeNoException(); reply.writeInt(result); return true; @@ -1618,25 +1620,19 @@ class ActivityManagerProxy implements IActivityManager } public int startActivity(IApplicationThread caller, Intent intent, - String resolvedType, Uri[] grantedUriPermissions, int grantedMode, - IBinder resultTo, String resultWho, - int requestCode, boolean onlyIfNeeded, - boolean debug, boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler) throws RemoteException { + String resolvedType, IBinder resultTo, String resultWho, int requestCode, + int startFlags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); intent.writeToParcel(data, 0); data.writeString(resolvedType); - data.writeTypedArray(grantedUriPermissions, 0); - data.writeInt(grantedMode); data.writeStrongBinder(resultTo); data.writeString(resultWho); data.writeInt(requestCode); - data.writeInt(onlyIfNeeded ? 1 : 0); - data.writeInt(debug ? 1 : 0); - data.writeInt(openglTrace ? 1 : 0); + data.writeInt(startFlags); data.writeString(profileFile); if (profileFd != null) { data.writeInt(1); @@ -1644,7 +1640,12 @@ class ActivityManagerProxy implements IActivityManager } else { data.writeInt(0); } - data.writeInt(autoStopProfiler ? 1 : 0); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); @@ -1653,25 +1654,19 @@ class ActivityManagerProxy implements IActivityManager return result; } public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent, - String resolvedType, Uri[] grantedUriPermissions, int grantedMode, - IBinder resultTo, String resultWho, - int requestCode, boolean onlyIfNeeded, - boolean debug, boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler) throws RemoteException { + String resolvedType, IBinder resultTo, String resultWho, + int requestCode, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); intent.writeToParcel(data, 0); data.writeString(resolvedType); - data.writeTypedArray(grantedUriPermissions, 0); - data.writeInt(grantedMode); data.writeStrongBinder(resultTo); data.writeString(resultWho); data.writeInt(requestCode); - data.writeInt(onlyIfNeeded ? 1 : 0); - data.writeInt(debug ? 1 : 0); - data.writeInt(openglTrace ? 1 : 0); + data.writeInt(startFlags); data.writeString(profileFile); if (profileFd != null) { data.writeInt(1); @@ -1679,7 +1674,12 @@ class ActivityManagerProxy implements IActivityManager } else { data.writeInt(0); } - data.writeInt(autoStopProfiler ? 1 : 0); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(START_ACTIVITY_AND_WAIT_TRANSACTION, data, reply, 0); reply.readException(); WaitResult result = WaitResult.CREATOR.createFromParcel(reply); @@ -1688,24 +1688,26 @@ class ActivityManagerProxy implements IActivityManager return result; } public int startActivityWithConfig(IApplicationThread caller, Intent intent, - String resolvedType, Uri[] grantedUriPermissions, int grantedMode, - IBinder resultTo, String resultWho, - int requestCode, boolean onlyIfNeeded, - boolean debug, Configuration config) throws RemoteException { + String resolvedType, IBinder resultTo, String resultWho, + int requestCode, int startFlags, Configuration config, + Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); intent.writeToParcel(data, 0); data.writeString(resolvedType); - data.writeTypedArray(grantedUriPermissions, 0); - data.writeInt(grantedMode); data.writeStrongBinder(resultTo); data.writeString(resultWho); data.writeInt(requestCode); - data.writeInt(onlyIfNeeded ? 1 : 0); - data.writeInt(debug ? 1 : 0); + data.writeInt(startFlags); config.writeToParcel(data, 0); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); @@ -1716,7 +1718,7 @@ class ActivityManagerProxy implements IActivityManager public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, - int flagsMask, int flagsValues) throws RemoteException { + int flagsMask, int flagsValues, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -1734,6 +1736,12 @@ class ActivityManagerProxy implements IActivityManager data.writeInt(requestCode); data.writeInt(flagsMask); data.writeInt(flagsValues); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(START_ACTIVITY_INTENT_SENDER_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); @@ -1742,12 +1750,18 @@ class ActivityManagerProxy implements IActivityManager return result; } public boolean startNextMatchingActivity(IBinder callingActivity, - Intent intent) throws RemoteException { + Intent intent, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(callingActivity); intent.writeToParcel(data, 0); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(START_NEXT_MATCHING_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); @@ -3075,7 +3089,7 @@ class ActivityManagerProxy implements IActivityManager public int startActivityInPackage(int uid, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded) + String resultWho, int requestCode, int startFlags, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -3086,7 +3100,13 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(resultTo); data.writeString(resultWho); data.writeInt(requestCode); - data.writeInt(onlyIfNeeded ? 1 : 0); + data.writeInt(startFlags); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(START_ACTIVITY_IN_PACKAGE_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); @@ -3339,7 +3359,8 @@ class ActivityManagerProxy implements IActivityManager } public int startActivities(IApplicationThread caller, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException { + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3347,6 +3368,12 @@ class ActivityManagerProxy implements IActivityManager data.writeTypedArray(intents, 0); data.writeStringArray(resolvedTypes); data.writeStrongBinder(resultTo); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(START_ACTIVITIES_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); @@ -3356,7 +3383,8 @@ class ActivityManagerProxy implements IActivityManager } public int startActivitiesInPackage(int uid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException { + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -3364,6 +3392,12 @@ class ActivityManagerProxy implements IActivityManager data.writeTypedArray(intents, 0); data.writeStringArray(resolvedTypes); data.writeStrongBinder(resultTo); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(START_ACTIVITIES_IN_PACKAGE_TRANSACTION, data, reply, 0); reply.readException(); int result = reply.readInt(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2610d8772de6..2a3e213c5003 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1802,7 +1802,7 @@ public final class ActivityThread { if (aInfo == null) { // Throw an exception. Instrumentation.checkStartActivityResult( - IActivityManager.START_CLASS_NOT_FOUND, intent); + ActivityManager.START_CLASS_NOT_FOUND, intent); } return aInfo; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index e348b87c4965..7043a73dc47a 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -852,6 +852,11 @@ class ContextImpl extends Context { @Override public void startActivity(Intent intent) { + startActivity(intent, null); + } + + @Override + public void startActivity(Intent intent, Bundle options) { if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivity() from outside of an Activity " @@ -860,11 +865,16 @@ class ContextImpl extends Context { } mMainThread.getInstrumentation().execStartActivity( getOuterContext(), mMainThread.getApplicationThread(), null, - (Activity)null, intent, -1); + (Activity)null, intent, -1, options); } @Override public void startActivities(Intent[] intents) { + startActivities(intents, null); + } + + @Override + public void startActivities(Intent[] intents, Bundle options) { if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { throw new AndroidRuntimeException( "Calling startActivities() from outside of an Activity " @@ -873,13 +883,20 @@ class ContextImpl extends Context { } mMainThread.getInstrumentation().execStartActivities( getOuterContext(), mMainThread.getApplicationThread(), null, - (Activity)null, intents); + (Activity)null, intents, options); } @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { + startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags, null); + } + + @Override + public void startIntentSender(IntentSender intent, Intent fillInIntent, + int flagsMask, int flagsValues, int extraFlags, Bundle options) + throws IntentSender.SendIntentException { try { String resolvedType = null; if (fillInIntent != null) { @@ -889,8 +906,8 @@ class ContextImpl extends Context { int result = ActivityManagerNative.getDefault() .startActivityIntentSender(mMainThread.getApplicationThread(), intent, fillInIntent, resolvedType, null, null, - 0, flagsMask, flagsValues); - if (result == IActivityManager.START_CANCELED) { + 0, flagsMask, flagsValues, options); + if (result == ActivityManager.START_CANCELED) { throw new IntentSender.SendIntentException(); } Instrumentation.checkStartActivityResult(result, null); diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 492fcc73540c..dadc4e5ff593 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -961,27 +961,55 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, true); return mLoaderManager; } - + /** * Call {@link Activity#startActivity(Intent)} on the fragment's * containing Activity. */ public void startActivity(Intent intent) { + startActivity(intent, null); + } + + /** + * Call {@link Activity#startActivity(Intent, Bundle)} on the fragment's + * containing Activity. + */ + public void startActivity(Intent intent, Bundle options) { if (mActivity == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - mActivity.startActivityFromFragment(this, intent, -1); + if (options != null) { + mActivity.startActivityFromFragment(this, intent, -1, options); + } else { + // Note we want to go through this call for compatibility with + // applications that may have overridden the method. + mActivity.startActivityFromFragment(this, intent, -1); + } } - + /** * Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's * containing Activity. */ public void startActivityForResult(Intent intent, int requestCode) { + startActivityForResult(intent, requestCode, null); + } + + /** + * Call {@link Activity#startActivityForResult(Intent, int, Bundle)} on the fragment's + * containing Activity. + */ + public void startActivityForResult(Intent intent, int requestCode, Bundle options) { if (mActivity == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - mActivity.startActivityFromFragment(this, intent, requestCode); + if (options != null) { + mActivity.startActivityFromFragment(this, intent, requestCode, options); + } else { + // Note we want to go through this call for compatibility with + // applications that may have overridden the method. + mActivity.startActivityFromFragment(this, intent, requestCode, options); + } } /** diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index acebf58a7191..9306bd2a1416 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -50,57 +50,24 @@ import java.util.List; * {@hide} */ public interface IActivityManager extends IInterface { - /** - * Returned by startActivity() if the start request was canceled because - * app switches are temporarily canceled to ensure the user's last request - * (such as pressing home) is performed. - */ - public static final int START_SWITCHES_CANCELED = 4; - /** - * Returned by startActivity() if an activity wasn't really started, but - * the given Intent was given to the existing top activity. - */ - public static final int START_DELIVERED_TO_TOP = 3; - /** - * Returned by startActivity() if an activity wasn't really started, but - * a task was simply brought to the foreground. - */ - public static final int START_TASK_TO_FRONT = 2; - /** - * Returned by startActivity() if the caller asked that the Intent not - * be executed if it is the recipient, and that is indeed the case. - */ - public static final int START_RETURN_INTENT_TO_CALLER = 1; - /** - * Activity was started successfully as normal. - */ - public static final int START_SUCCESS = 0; - public static final int START_INTENT_NOT_RESOLVED = -1; - public static final int START_CLASS_NOT_FOUND = -2; - public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3; - public static final int START_PERMISSION_DENIED = -4; - public static final int START_NOT_ACTIVITY = -5; - public static final int START_CANCELED = -6; public int startActivity(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, String resultWho, int requestCode, - boolean onlyIfNeeded, boolean debug, boolean openglTrace, String profileFile, - ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException; + Intent intent, String resolvedType, IBinder resultTo, String resultWho, + int requestCode, int flags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options) throws RemoteException; public WaitResult startActivityAndWait(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, String resultWho, int requestCode, - boolean onlyIfNeeded, boolean debug, boolean openglTrace, String profileFile, - ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException; + Intent intent, String resolvedType, IBinder resultTo, String resultWho, + int requestCode, int flags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options) throws RemoteException; public int startActivityWithConfig(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, String resultWho, int requestCode, - boolean onlyIfNeeded, boolean debug, Configuration newConfig) throws RemoteException; + Intent intent, String resolvedType, IBinder resultTo, String resultWho, + int requestCode, int startFlags, Configuration newConfig, + Bundle options) throws RemoteException; public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, - int flagsMask, int flagsValues) throws RemoteException; + int flagsMask, int flagsValues, Bundle options) throws RemoteException; public boolean startNextMatchingActivity(IBinder callingActivity, - Intent intent) throws RemoteException; + Intent intent, Bundle options) throws RemoteException; public boolean finishActivity(IBinder token, int code, Intent data) throws RemoteException; public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException; @@ -109,8 +76,6 @@ public interface IActivityManager extends IInterface { IIntentReceiver receiver, IntentFilter filter, String requiredPermission) throws RemoteException; public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException; - public static final int BROADCAST_SUCCESS = 0; - public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1; public int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, @@ -201,10 +166,6 @@ public interface IActivityManager extends IInterface { public ComponentName getActivityClassForToken(IBinder token) throws RemoteException; public String getPackageForToken(IBinder token) throws RemoteException; - public static final int INTENT_SENDER_BROADCAST = 1; - public static final int INTENT_SENDER_ACTIVITY = 2; - public static final int INTENT_SENDER_ACTIVITY_RESULT = 3; - public static final int INTENT_SENDER_SERVICE = 4; public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, @@ -302,7 +263,7 @@ public interface IActivityManager extends IInterface { public int startActivityInPackage(int uid, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded) + String resultWho, int requestCode, int startFlags, Bundle options) throws RemoteException; public void killApplicationWithUid(String pkg, int uid) throws RemoteException; @@ -342,9 +303,11 @@ public interface IActivityManager extends IInterface { ParcelFileDescriptor fd) throws RemoteException; public int startActivities(IApplicationThread caller, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException; + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options) throws RemoteException; public int startActivitiesInPackage(int uid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException; + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options) throws RemoteException; public int getFrontActivityScreenCompatMode() throws RemoteException; public void setFrontActivityScreenCompatMode(int mode) throws RemoteException; diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index a34e1d38064a..16299de6fa3c 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1346,6 +1346,7 @@ public class Instrumentation { * @param intent The actual Intent to start. * @param requestCode Identifier for this request's result; less than zero * if the caller is not expecting a result. + * @param options Addition options. * * @return To force the return of a particular result, return an * ActivityResult object containing the desired data; otherwise @@ -1361,7 +1362,7 @@ public class Instrumentation { */ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode) { + Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1383,8 +1384,8 @@ public class Instrumentation { int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), - null, 0, token, target != null ? target.mEmbeddedID : null, - requestCode, false, false, false, null, null, false); + token, target != null ? target.mEmbeddedID : null, + requestCode, 0, null, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { } @@ -1400,7 +1401,7 @@ public class Instrumentation { * {@hide} */ public void execStartActivities(Context who, IBinder contextThread, - IBinder token, Activity target, Intent[] intents) { + IBinder token, Activity target, Intent[] intents, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1424,7 +1425,7 @@ public class Instrumentation { resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver()); } int result = ActivityManagerNative.getDefault() - .startActivities(whoThread, intents, resolvedTypes, token); + .startActivities(whoThread, intents, resolvedTypes, token, options); checkStartActivityResult(result, intents[0]); } catch (RemoteException e) { } @@ -1459,7 +1460,7 @@ public class Instrumentation { */ public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Fragment target, - Intent intent, int requestCode) { + Intent intent, int requestCode, Bundle options) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1481,9 +1482,8 @@ public class Instrumentation { int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), - null, 0, token, target != null ? target.mWho : null, - requestCode, false, false /* debug */, false /* openglTrace */, - null, null, false); + token, target != null ? target.mWho : null, + requestCode, 0, null, null, options); checkStartActivityResult(result, intent); } catch (RemoteException e) { } @@ -1502,13 +1502,13 @@ public class Instrumentation { } /*package*/ static void checkStartActivityResult(int res, Object intent) { - if (res >= IActivityManager.START_SUCCESS) { + if (res >= ActivityManager.START_SUCCESS) { return; } switch (res) { - case IActivityManager.START_INTENT_NOT_RESOLVED: - case IActivityManager.START_CLASS_NOT_FOUND: + case ActivityManager.START_INTENT_NOT_RESOLVED: + case ActivityManager.START_CLASS_NOT_FOUND: if (intent instanceof Intent && ((Intent)intent).getComponent() != null) throw new ActivityNotFoundException( "Unable to find explicit activity class " @@ -1516,13 +1516,13 @@ public class Instrumentation { + "; have you declared this activity in your AndroidManifest.xml?"); throw new ActivityNotFoundException( "No Activity found to handle " + intent); - case IActivityManager.START_PERMISSION_DENIED: + case ActivityManager.START_PERMISSION_DENIED: throw new SecurityException("Not allowed to start activity " + intent); - case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: + case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: throw new AndroidRuntimeException( "FORWARD_RESULT_FLAG used while also requesting a result"); - case IActivityManager.START_NOT_ACTIVITY: + case ActivityManager.START_NOT_ACTIVITY: throw new IllegalArgumentException( "PendingIntent is not an activity"); default: diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index c95066cd61ba..57192c3c45e3 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -195,7 +195,7 @@ public final class PendingIntent implements Parcelable { intent.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( - IActivityManager.INTENT_SENDER_ACTIVITY, packageName, + ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags); return target != null ? new PendingIntent(target) : null; @@ -256,7 +256,7 @@ public final class PendingIntent implements Parcelable { try { IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( - IActivityManager.INTENT_SENDER_ACTIVITY, packageName, + ActivityManager.INTENT_SENDER_ACTIVITY, packageName, null, null, requestCode, intents, resolvedTypes, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { @@ -292,7 +292,7 @@ public final class PendingIntent implements Parcelable { intent.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( - IActivityManager.INTENT_SENDER_BROADCAST, packageName, + ActivityManager.INTENT_SENDER_BROADCAST, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags); return target != null ? new PendingIntent(target) : null; @@ -330,7 +330,7 @@ public final class PendingIntent implements Parcelable { intent.setAllowFds(false); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( - IActivityManager.INTENT_SENDER_SERVICE, packageName, + ActivityManager.INTENT_SENDER_SERVICE, packageName, null, null, requestCode, new Intent[] { intent }, resolvedType != null ? new String[] { resolvedType } : null, flags); return target != null ? new PendingIntent(target) : null; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 0e9e256af0b3..19a5bc0103fd 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -818,6 +818,19 @@ public abstract class Context { public abstract void clearWallpaper() throws IOException; /** + * Same as {@link #startActivity(Intent, Bundle)} with no options + * specified. + * + * @param intent The description of the activity to start. + * + * @throws ActivityNotFoundException + * + * @see {@link #startActivity(Intent, Bundle)} + * @see PackageManager#resolveActivity + */ + public abstract void startActivity(Intent intent); + + /** * Launch a new activity. You will not receive any information about when * the activity exits. * @@ -832,12 +845,28 @@ public abstract class Context { * if there was no Activity found to run the given Intent. * * @param intent The description of the activity to start. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. * * @throws ActivityNotFoundException * + * @see {@link #startActivity(Intent)} * @see PackageManager#resolveActivity */ - public abstract void startActivity(Intent intent); + public abstract void startActivity(Intent intent, Bundle options); + + /** + * Same as {@link #startActivities(Intent[], Bundle)} with no options + * specified. + * + * @param intents An array of Intents to be started. + * + * @throws ActivityNotFoundException + * + * @see {@link #startActivities(Intent[], Bundle)} + * @see PackageManager#resolveActivity + */ + public abstract void startActivities(Intent[] intents); /** * Launch multiple new activities. This is generally the same as calling @@ -854,15 +883,38 @@ public abstract class Context { * list may be on it, some not), so you probably want to avoid such situations. * * @param intents An array of Intents to be started. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. * * @throws ActivityNotFoundException * + * @see {@link #startActivities(Intent[])} * @see PackageManager#resolveActivity */ - public abstract void startActivities(Intent[] intents); + public abstract void startActivities(Intent[] intents, Bundle options); + + /** + * Same as {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)} + * with no options specified. + * + * @param intent The IntentSender to launch. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * <var>flagsMask</var> + * @param extraFlags Always set to 0. + * + * @see #startActivity(Intent) + * @see #startIntentSender(IntentSender, Intent, int, int, int, Bundle) + */ + public abstract void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException; /** - * Like {@link #startActivity(Intent)}, but taking a IntentSender + * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender * to start. If the IntentSender is for an activity, that activity will be started * as if you had called the regular {@link #startActivity(Intent)} * here; otherwise, its associated action will be executed (such as @@ -877,10 +929,15 @@ public abstract class Context { * @param flagsValues Desired values for any bits set in * <var>flagsMask</var> * @param extraFlags Always set to 0. + * @param options Additional options for how the Activity should be started. + * May be null if there are no options. + * + * @see #startActivity(Intent, Bundle) + * @see #startIntentSender(IntentSender, Intent, int, int, int) */ public abstract void startIntentSender(IntentSender intent, - Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) - throws IntentSender.SendIntentException; + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException; /** * Broadcast the given intent to all interested BroadcastReceivers. This diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 5ba9dccd36b3..6b950e0eaa39 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -277,17 +277,35 @@ public class ContextWrapper extends Context { } @Override + public void startActivity(Intent intent, Bundle options) { + mBase.startActivity(intent, options); + } + + @Override public void startActivities(Intent[] intents) { mBase.startActivities(intents); } @Override + public void startActivities(Intent[] intents, Bundle options) { + mBase.startActivities(intents, options); + } + + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { mBase.startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags); } + + @Override + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException { + mBase.startIntentSender(intent, fillInIntent, flagsMask, + flagsValues, extraFlags, options); + } @Override public void sendBroadcast(Intent intent) { diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index fbcbb322bad8..aa92efb0c932 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -437,6 +437,13 @@ public class Camera { * instances of the same camera, or across multiple runs of the same * program. * + * <p>If you are using the preview data to create video or still images, + * strongly consider using {@link android.media.MediaActionSound} to + * properly indicate image capture or recording start/stop to the user.</p> + * + * @see android.media.MediaActionSound + * @see android.graphics.SurfaceTexture + * @see android.view.TextureView * @param surfaceTexture the {@link SurfaceTexture} to which the preview * images are to be sent or null to remove the current preview surface * texture @@ -512,13 +519,19 @@ public class Camera { public native final boolean previewEnabled(); /** - * Installs a callback to be invoked for every preview frame in addition + * <p>Installs a callback to be invoked for every preview frame in addition * to displaying them on the screen. The callback will be repeatedly called * for as long as preview is active. This method can be called at any time, - * even while preview is live. Any other preview callbacks are overridden. + * even while preview is live. Any other preview callbacks are + * overridden.</p> + * + * <p>If you are using the preview data to create video or still images, + * strongly consider using {@link android.media.MediaActionSound} to + * properly indicate image capture or recording start/stop to the user.</p> * * @param cb a callback object that receives a copy of each preview frame, * or null to stop receiving callbacks. + * @see android.media.MediaActionSound */ public final void setPreviewCallback(PreviewCallback cb) { mPreviewCallback = cb; @@ -530,13 +543,18 @@ public class Camera { } /** - * Installs a callback to be invoked for the next preview frame in addition - * to displaying it on the screen. After one invocation, the callback is - * cleared. This method can be called any time, even when preview is live. - * Any other preview callbacks are overridden. + * <p>Installs a callback to be invoked for the next preview frame in + * addition to displaying it on the screen. After one invocation, the + * callback is cleared. This method can be called any time, even when + * preview is live. Any other preview callbacks are overridden.</p> + * + * <p>If you are using the preview data to create video or still images, + * strongly consider using {@link android.media.MediaActionSound} to + * properly indicate image capture or recording start/stop to the user.</p> * * @param cb a callback object that receives a copy of the next preview frame, * or null to stop receiving callbacks. + * @see android.media.MediaActionSound */ public final void setOneShotPreviewCallback(PreviewCallback cb) { mPreviewCallback = cb; @@ -548,24 +566,30 @@ public class Camera { private native final void setHasPreviewCallback(boolean installed, boolean manualBuffer); /** - * Installs a callback to be invoked for every preview frame, using buffers - * supplied with {@link #addCallbackBuffer(byte[])}, in addition to + * <p>Installs a callback to be invoked for every preview frame, using + * buffers supplied with {@link #addCallbackBuffer(byte[])}, in addition to * displaying them on the screen. The callback will be repeatedly called - * for as long as preview is active and buffers are available. - * Any other preview callbacks are overridden. + * for as long as preview is active and buffers are available. Any other + * preview callbacks are overridden.</p> * * <p>The purpose of this method is to improve preview efficiency and frame * rate by allowing preview frame memory reuse. You must call * {@link #addCallbackBuffer(byte[])} at some point -- before or after - * calling this method -- or no callbacks will received. + * calling this method -- or no callbacks will received.</p> * - * The buffer queue will be cleared if this method is called with a null + * <p>The buffer queue will be cleared if this method is called with a null * callback, {@link #setPreviewCallback(Camera.PreviewCallback)} is called, - * or {@link #setOneShotPreviewCallback(Camera.PreviewCallback)} is called. + * or {@link #setOneShotPreviewCallback(Camera.PreviewCallback)} is + * called.</p> + * + * <p>If you are using the preview data to create video or still images, + * strongly consider using {@link android.media.MediaActionSound} to + * properly indicate image capture or recording start/stop to the user.</p> * * @param cb a callback object that receives a copy of the preview frame, * or null to stop receiving callbacks and clear the buffer queue. * @see #addCallbackBuffer(byte[]) + * @see android.media.MediaActionSound */ public final void setPreviewCallbackWithBuffer(PreviewCallback cb) { mPreviewCallback = cb; @@ -834,10 +858,15 @@ public class Camera { * the focus position. Applications must call cancelAutoFocus to reset the * focus.</p> * + * <p>If autofocus is successful, consider using + * {@link android.media.MediaActionSound} to properly play back an autofocus + * success sound to the user.</p> + * * @param cb the callback to run * @see #cancelAutoFocus() * @see android.hardware.Camera.Parameters#setAutoExposureLock(boolean) * @see android.hardware.Camera.Parameters#setAutoWhiteBalanceLock(boolean) + * @see android.media.MediaActionSound */ public final void autoFocus(AutoFocusCallback cb) { diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 0e6d07d8dd18..961215184170 100755 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -596,8 +596,8 @@ public final class Telephony { * values:</p> * * <ul> - * <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs - * that make up the message.</li> + * <li><em>message</em> - An SmsCbMessage object containing the broadcast message + * data. This is not an emergency alert, so ETWS and CMAS data will be null.</li> * </ul> * * <p>The extra values can be extracted using @@ -616,8 +616,8 @@ public final class Telephony { * values:</p> * * <ul> - * <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs - * that make up the message.</li> + * <li><em>message</em> - An SmsCbMessage object containing the broadcast message + * data, including ETWS or CMAS warning notification info if present.</li> * </ul> * * <p>The extra values can be extracted using @@ -631,6 +631,26 @@ public final class Telephony { "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; /** + * Broadcast Action: A new CDMA SMS has been received containing Service Category + * Program Data (updates the list of enabled broadcast channels). The intent will + * have the following extra values:</p> + * + * <ul> + * <li><em>operations</em> - An array of CdmaSmsCbProgramData objects containing + * the service category operations (add/delete/clear) to perform.</li> + * </ul> + * + * <p>The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.</p> + * + * <p>If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.</p> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION = + "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED"; + + /** * Broadcast Action: The SIM storage for SMS messages is full. If * space is not freed, messages targeted for the SIM (class 2) may * not be saved. diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 516ce2ad695a..2dcea80c5b21 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1,4 +1,4 @@ - /* +/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -363,40 +363,67 @@ public abstract class Layout { // direction of the layout or line. XXX: Should they? // They are evaluated at each line. if (mSpannedText) { - int previousLineBottom = getLineTop(firstLine); - int previousLineEnd = getLineStart(firstLine); - ParagraphStyle[] spans = NO_PARA_SPANS; - TextPaint paint = mPaint; - CharSequence buf = mText; - int spanEnd = 0; - final int width = mWidth; - Spanned sp = (Spanned) buf; - int textLength = buf.length(); - for (int i = firstLine; i <= lastLine; i++) { - int start = previousLineEnd; - int end = getLineStart(i + 1); - previousLineEnd = end; - - int ltop = previousLineBottom; - int lbottom = getLineTop(i + 1); - previousLineBottom = lbottom; - int lbaseline = lbottom - getLineDescent(i); - - if (start >= spanEnd) { - // These should be infrequent, so we'll use this so that - // we don't have to check as often. - spanEnd = sp.nextSpanTransition(start, textLength, LineBackgroundSpan.class); - // All LineBackgroundSpans on a line contribute to its background. - spans = getParagraphSpans(sp, start, end, LineBackgroundSpan.class); - } + if (lineBackgroundSpans == null) { + lineBackgroundSpans = new SpanSet<LineBackgroundSpan>(LineBackgroundSpan.class); + } - for (int n = 0; n < spans.length; n++) { - LineBackgroundSpan back = (LineBackgroundSpan) spans[n]; - back.drawBackground(canvas, paint, 0, width, - ltop, lbaseline, lbottom, - buf, start, end, i); + Spanned buffer = (Spanned) mText; + int textLength = buffer.length(); + lineBackgroundSpans.init(buffer, 0, textLength); + + if (lineBackgroundSpans.numberOfSpans > 0) { + int previousLineBottom = getLineTop(firstLine); + int previousLineEnd = getLineStart(firstLine); + ParagraphStyle[] spans = NO_PARA_SPANS; + int spansLength = 0; + TextPaint paint = mPaint; + int spanEnd = 0; + final int width = mWidth; + for (int i = firstLine; i <= lastLine; i++) { + int start = previousLineEnd; + int end = getLineStart(i + 1); + previousLineEnd = end; + + int ltop = previousLineBottom; + int lbottom = getLineTop(i + 1); + previousLineBottom = lbottom; + int lbaseline = lbottom - getLineDescent(i); + + if (start >= spanEnd) { + // These should be infrequent, so we'll use this so that + // we don't have to check as often. + spanEnd = lineBackgroundSpans.getNextTransition(start, textLength); + // All LineBackgroundSpans on a line contribute to its background. + spansLength = 0; + // Duplication of the logic of getParagraphSpans + if (start != end || start == 0) { + // Equivalent to a getSpans(start, end), but filling the 'spans' local + // array instead to reduce memory allocation + for (int j = 0; j < lineBackgroundSpans.numberOfSpans; j++) { + // equal test is valid since both intervals are not empty by construction + if (lineBackgroundSpans.spanStarts[j] >= end || + lineBackgroundSpans.spanEnds[j] <= start) continue; + if (spansLength == spans.length) { + // The spans array needs to be expanded + int newSize = ArrayUtils.idealObjectArraySize(2 * spansLength); + ParagraphStyle[] newSpans = new ParagraphStyle[newSize]; + System.arraycopy(spans, 0, newSpans, 0, spansLength); + spans = newSpans; + } + spans[spansLength++] = lineBackgroundSpans.spans[j]; + } + } + } + + for (int n = 0; n < spansLength; n++) { + LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n]; + lineBackgroundSpan.drawBackground(canvas, paint, 0, width, + ltop, lbaseline, lbottom, + buffer, start, end, i); + } } } + lineBackgroundSpans.recycle(); } // There can be a highlight even without spans if we are drawing @@ -1830,6 +1857,7 @@ public abstract class Layout { private static final Rect sTempRect = new Rect(); private boolean mSpannedText; private TextDirectionHeuristic mTextDir; + private SpanSet<LineBackgroundSpan> lineBackgroundSpans; public static final int DIR_LEFT_TO_RIGHT = 1; public static final int DIR_RIGHT_TO_LEFT = -1; diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index fac549d757f3..730ad08db348 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -112,6 +112,18 @@ public class HTML5VideoFullScreen extends HTML5VideoView } }; + MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = + new MediaPlayer.OnVideoSizeChangedListener() { + @Override + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + if (mVideoWidth != 0 && mVideoHeight != 0) { + mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); + } + } + }; + private SurfaceView getSurfaceView() { return mVideoSurfaceView; } @@ -150,6 +162,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView mc.setSystemUiVisibility(mLayout.getSystemUiVisibility()); setMediaController(mc); mPlayer.setScreenOnWhilePlaying(true); + mPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); prepareDataAndDisplayMode(mProxy); } diff --git a/core/java/com/android/internal/util/BitwiseOutputStream.java b/core/java/com/android/internal/util/BitwiseOutputStream.java index 70c0be81a56a..ddecbed1d97c 100644 --- a/core/java/com/android/internal/util/BitwiseOutputStream.java +++ b/core/java/com/android/internal/util/BitwiseOutputStream.java @@ -77,6 +77,7 @@ public class BitwiseOutputStream { byte[] newBuf = new byte[(mPos + bits) >>> 2]; System.arraycopy(mBuf, 0, newBuf, 0, mEnd >>> 3); mBuf = newBuf; + mEnd = newBuf.length << 3; } /** diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 7984b9c6311f..480c3a660a71 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -23,13 +23,13 @@ #include <fcntl.h> #include <math.h> -#include "jni.h" -#include "JNIHelp.h" -#include "android_runtime/AndroidRuntime.h" +#include <jni.h> +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> -#include "utils/Log.h" -#include "media/AudioRecord.h" -#include "media/mediarecorder.h" +#include <utils/Log.h> +#include <media/AudioRecord.h> +#include <media/mediarecorder.h> #include <cutils/bitops.h> diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index bff59945893b..f522a9a07f73 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -16,16 +16,16 @@ */ #define LOG_TAG "AudioSystem" -#include "utils/Log.h" +#include <utils/Log.h> #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <math.h> -#include "jni.h" -#include "JNIHelp.h" -#include "android_runtime/AndroidRuntime.h" +#include <jni.h> +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> #include <media/AudioSystem.h> diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index d4ed06c30812..9fbc477a86fe 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -22,13 +22,13 @@ #include <fcntl.h> #include <math.h> -#include "jni.h" -#include "JNIHelp.h" -#include "android_runtime/AndroidRuntime.h" +#include <jni.h> +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> -#include "utils/Log.h" -#include "media/AudioSystem.h" -#include "media/AudioTrack.h" +#include <utils/Log.h> +#include <media/AudioSystem.h> +#include <media/AudioTrack.h> #include <binder/MemoryHeapBase.h> #include <binder/MemoryBase.h> diff --git a/core/jni/android_media_JetPlayer.cpp b/core/jni/android_media_JetPlayer.cpp index 6fedc6b97ba0..a785f5213362 100644 --- a/core/jni/android_media_JetPlayer.cpp +++ b/core/jni/android_media_JetPlayer.cpp @@ -22,12 +22,12 @@ #include <unistd.h> #include <fcntl.h> -#include "jni.h" -#include "JNIHelp.h" -#include "android_runtime/AndroidRuntime.h" +#include <jni.h> +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> -#include "utils/Log.h" -#include "media/JetPlayer.h" +#include <utils/Log.h> +#include <media/JetPlayer.h> using namespace android; diff --git a/core/jni/android_media_ToneGenerator.cpp b/core/jni/android_media_ToneGenerator.cpp index 544b4c09661f..31151a62f13c 100644 --- a/core/jni/android_media_ToneGenerator.cpp +++ b/core/jni/android_media_ToneGenerator.cpp @@ -21,13 +21,13 @@ #include <unistd.h> #include <fcntl.h> -#include "jni.h" -#include "JNIHelp.h" -#include "android_runtime/AndroidRuntime.h" +#include <jni.h> +#include <JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> -#include "utils/Log.h" -#include "media/AudioSystem.h" -#include "media/ToneGenerator.h" +#include <utils/Log.h> +#include <media/AudioSystem.h> +#include <media/ToneGenerator.h> // ---------------------------------------------------------------------------- diff --git a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java index a304b68145df..306f58fcce2e 100644 --- a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java @@ -133,4 +133,25 @@ public class BitwiseStreamsTest extends AndroidTestCase { long end = android.os.SystemClock.elapsedRealtime(); Log.d(LOG_TAG, "repeated encode-decode took " + (end - start) + " ms"); } + + @SmallTest + public void testExpandArray() throws Exception { + Random random = new Random(); + int iterations = 10000; + int[] sizeArr = new int[iterations]; + int[] valueArr = new int[iterations]; + BitwiseOutputStream outStream = new BitwiseOutputStream(8); + for (int i = 0; i < iterations; i++) { + int x = random.nextInt(); + int size = (x & 0x07) + 1; + int value = x & (-1 >>> (32 - size)); + sizeArr[i] = size; + valueArr[i] = value; + outStream.write(size, value); + } + BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray()); + for (int i = 0; i < iterations; i++) { + assertEquals(valueArr[i], inStream.read(sizeArr[i])); + } + } } diff --git a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp index 5ee41e6649dc..4b1b40ebd2b6 100644 --- a/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp +++ b/drm/libdrmframework/plugins/forward-lock/FwdLockEngine/src/FwdLockEngine.cpp @@ -159,11 +159,13 @@ android::status_t FwdLockEngine::onTerminate(int uniqueId) { return DRM_NO_ERROR; } +// make sure that lower-case letters are used. const String8 FwdLockEngine::FileSuffixes[] = { String8(".fl"), String8(".dm"), }; +// make sure that lower-case letters are used. const String8 FwdLockEngine::MimeTypes[] = { String8("application/x-android-drm-fl"), String8("application/vnd.oma.drm.message"), @@ -184,8 +186,10 @@ void FwdLockEngine::AddSupportedFileSuffixes(DrmSupportInfo *info) { } bool FwdLockEngine::IsMimeTypeSupported(const String8& mime) { + String8 tmp(mime); + tmp.toLower(); for (size_t i = 0, n = sizeof(MimeTypes)/sizeof(MimeTypes[0]); i < n; ++i) { - if (mime == MimeTypes[i]) { + if (tmp == MimeTypes[i]) { return true; } } @@ -193,8 +197,10 @@ bool FwdLockEngine::IsMimeTypeSupported(const String8& mime) { } bool FwdLockEngine::IsFileSuffixSupported(const String8& suffix) { + String8 tmp(suffix); + tmp.toLower(); for (size_t i = 0, n = sizeof(FileSuffixes)/sizeof(FileSuffixes[0]); i < n; ++i) { - if (suffix == FileSuffixes[i]) { + if (tmp == FileSuffixes[i]) { return true; } } @@ -220,7 +226,6 @@ bool FwdLockEngine::onCanHandle(int uniqueId, const String8& path) { bool result = false; String8 extString = path.getPathExtension(); - extString.toLower(); return IsFileSuffixSupported(extString); } @@ -331,8 +336,6 @@ int FwdLockEngine::onGetDrmObjectType(int uniqueId, LOG_VERBOSE("FwdLockEngine::onGetDrmObjectType"); - mimeStr.toLower(); - /* Checks whether * 1. path and mime type both are not empty strings (meaning unavailable) else content is unknown * 2. if one of them is empty string and if other is known then its a DRM Content Object. diff --git a/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp b/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp index 0ffc0a7bed6b..a3eac3e0da72 100644 --- a/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp +++ b/drm/libdrmframework/plugins/passthru/src/DrmPassthruPlugIn.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_NDEBUG 0 +//#define LOG_NDEBUG 0 #define LOG_TAG "DrmPassthruPlugIn" #include <utils/Log.h> @@ -58,7 +58,7 @@ DrmMetadata* DrmPassthruPlugIn::onGetMetadata(int uniqueId, const String8* path) DrmConstraints* DrmPassthruPlugIn::onGetConstraints( int uniqueId, const String8* path, int action) { - ALOGD("DrmPassthruPlugIn::onGetConstraints From Path: %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onGetConstraints From Path: %d", uniqueId); DrmConstraints* drmConstraints = new DrmConstraints(); String8 value("dummy_available_time"); @@ -73,7 +73,7 @@ DrmConstraints* DrmPassthruPlugIn::onGetConstraints( } DrmInfoStatus* DrmPassthruPlugIn::onProcessDrmInfo(int uniqueId, const DrmInfo* drmInfo) { - ALOGD("DrmPassthruPlugIn::onProcessDrmInfo - Enter : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onProcessDrmInfo - Enter : %d", uniqueId); DrmInfoStatus* drmInfoStatus = NULL; if (NULL != drmInfo) { switch (drmInfo->getInfoType()) { @@ -102,28 +102,28 @@ DrmInfoStatus* DrmPassthruPlugIn::onProcessDrmInfo(int uniqueId, const DrmInfo* } } } - ALOGD("DrmPassthruPlugIn::onProcessDrmInfo - Exit"); + ALOGV("DrmPassthruPlugIn::onProcessDrmInfo - Exit"); return drmInfoStatus; } status_t DrmPassthruPlugIn::onSetOnInfoListener( int uniqueId, const IDrmEngine::OnInfoListener* infoListener) { - ALOGD("DrmPassthruPlugIn::onSetOnInfoListener : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onSetOnInfoListener : %d", uniqueId); return DRM_NO_ERROR; } status_t DrmPassthruPlugIn::onInitialize(int uniqueId) { - ALOGD("DrmPassthruPlugIn::onInitialize : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onInitialize : %d", uniqueId); return DRM_NO_ERROR; } status_t DrmPassthruPlugIn::onTerminate(int uniqueId) { - ALOGD("DrmPassthruPlugIn::onTerminate : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onTerminate : %d", uniqueId); return DRM_NO_ERROR; } DrmSupportInfo* DrmPassthruPlugIn::onGetSupportInfo(int uniqueId) { - ALOGD("DrmPassthruPlugIn::onGetSupportInfo : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onGetSupportInfo : %d", uniqueId); DrmSupportInfo* drmSupportInfo = new DrmSupportInfo(); // Add mimetype's drmSupportInfo->addMimeType(String8("application/vnd.passthru.drm")); @@ -136,12 +136,12 @@ DrmSupportInfo* DrmPassthruPlugIn::onGetSupportInfo(int uniqueId) { status_t DrmPassthruPlugIn::onSaveRights(int uniqueId, const DrmRights& drmRights, const String8& rightsPath, const String8& contentPath) { - ALOGD("DrmPassthruPlugIn::onSaveRights : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onSaveRights : %d", uniqueId); return DRM_NO_ERROR; } DrmInfo* DrmPassthruPlugIn::onAcquireDrmInfo(int uniqueId, const DrmInfoRequest* drmInfoRequest) { - ALOGD("DrmPassthruPlugIn::onAcquireDrmInfo : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onAcquireDrmInfo : %d", uniqueId); DrmInfo* drmInfo = NULL; if (NULL != drmInfoRequest) { @@ -157,65 +157,65 @@ DrmInfo* DrmPassthruPlugIn::onAcquireDrmInfo(int uniqueId, const DrmInfoRequest* } bool DrmPassthruPlugIn::onCanHandle(int uniqueId, const String8& path) { - ALOGD("DrmPassthruPlugIn::canHandle: %s ", path.string()); + ALOGV("DrmPassthruPlugIn::canHandle: %s ", path.string()); String8 extension = path.getPathExtension(); extension.toLower(); return (String8(".passthru") == extension); } String8 DrmPassthruPlugIn::onGetOriginalMimeType(int uniqueId, const String8& path) { - ALOGD("DrmPassthruPlugIn::onGetOriginalMimeType() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onGetOriginalMimeType() : %d", uniqueId); return String8("video/passthru"); } int DrmPassthruPlugIn::onGetDrmObjectType( int uniqueId, const String8& path, const String8& mimeType) { - ALOGD("DrmPassthruPlugIn::onGetDrmObjectType() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onGetDrmObjectType() : %d", uniqueId); return DrmObjectType::UNKNOWN; } int DrmPassthruPlugIn::onCheckRightsStatus(int uniqueId, const String8& path, int action) { - ALOGD("DrmPassthruPlugIn::onCheckRightsStatus() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onCheckRightsStatus() : %d", uniqueId); int rightsStatus = RightsStatus::RIGHTS_VALID; return rightsStatus; } status_t DrmPassthruPlugIn::onConsumeRights(int uniqueId, DecryptHandle* decryptHandle, int action, bool reserve) { - ALOGD("DrmPassthruPlugIn::onConsumeRights() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onConsumeRights() : %d", uniqueId); return DRM_NO_ERROR; } status_t DrmPassthruPlugIn::onSetPlaybackStatus(int uniqueId, DecryptHandle* decryptHandle, int playbackStatus, int64_t position) { - ALOGD("DrmPassthruPlugIn::onSetPlaybackStatus() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onSetPlaybackStatus() : %d", uniqueId); return DRM_NO_ERROR; } bool DrmPassthruPlugIn::onValidateAction(int uniqueId, const String8& path, int action, const ActionDescription& description) { - ALOGD("DrmPassthruPlugIn::onValidateAction() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onValidateAction() : %d", uniqueId); return true; } status_t DrmPassthruPlugIn::onRemoveRights(int uniqueId, const String8& path) { - ALOGD("DrmPassthruPlugIn::onRemoveRights() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onRemoveRights() : %d", uniqueId); return DRM_NO_ERROR; } status_t DrmPassthruPlugIn::onRemoveAllRights(int uniqueId) { - ALOGD("DrmPassthruPlugIn::onRemoveAllRights() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onRemoveAllRights() : %d", uniqueId); return DRM_NO_ERROR; } status_t DrmPassthruPlugIn::onOpenConvertSession(int uniqueId, int convertId) { - ALOGD("DrmPassthruPlugIn::onOpenConvertSession() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onOpenConvertSession() : %d", uniqueId); return DRM_NO_ERROR; } DrmConvertedStatus* DrmPassthruPlugIn::onConvertData( int uniqueId, int convertId, const DrmBuffer* inputData) { - ALOGD("DrmPassthruPlugIn::onConvertData() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onConvertData() : %d", uniqueId); DrmBuffer* convertedData = NULL; if (NULL != inputData && 0 < inputData->length) { @@ -229,13 +229,13 @@ DrmConvertedStatus* DrmPassthruPlugIn::onConvertData( } DrmConvertedStatus* DrmPassthruPlugIn::onCloseConvertSession(int uniqueId, int convertId) { - ALOGD("DrmPassthruPlugIn::onCloseConvertSession() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onCloseConvertSession() : %d", uniqueId); return new DrmConvertedStatus(DrmConvertedStatus::STATUS_OK, NULL, 0 /*offset*/); } status_t DrmPassthruPlugIn::onOpenDecryptSession( int uniqueId, DecryptHandle* decryptHandle, int fd, off64_t offset, off64_t length) { - ALOGD("DrmPassthruPlugIn::onOpenDecryptSession() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onOpenDecryptSession() : %d", uniqueId); #ifdef ENABLE_PASSTHRU_DECRYPTION decryptHandle->mimeType = String8("video/passthru"); @@ -254,7 +254,7 @@ status_t DrmPassthruPlugIn::onOpenDecryptSession( } status_t DrmPassthruPlugIn::onCloseDecryptSession(int uniqueId, DecryptHandle* decryptHandle) { - ALOGD("DrmPassthruPlugIn::onCloseDecryptSession() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onCloseDecryptSession() : %d", uniqueId); if (NULL != decryptHandle) { if (NULL != decryptHandle->decryptInfo) { delete decryptHandle->decryptInfo; decryptHandle->decryptInfo = NULL; @@ -266,34 +266,40 @@ status_t DrmPassthruPlugIn::onCloseDecryptSession(int uniqueId, DecryptHandle* d status_t DrmPassthruPlugIn::onInitializeDecryptUnit(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, const DrmBuffer* headerInfo) { - ALOGD("DrmPassthruPlugIn::onInitializeDecryptUnit() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onInitializeDecryptUnit() : %d", uniqueId); return DRM_NO_ERROR; } status_t DrmPassthruPlugIn::onDecrypt(int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId, const DrmBuffer* encBuffer, DrmBuffer** decBuffer, DrmBuffer* IV) { - ALOGD("DrmPassthruPlugIn::onDecrypt() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onDecrypt() : %d", uniqueId); /** * As a workaround implementation passthru would copy the given * encrypted buffer as it is to decrypted buffer. Note, decBuffer * memory has to be allocated by the caller. */ if (NULL != (*decBuffer) && 0 < (*decBuffer)->length) { - memcpy((*decBuffer)->data, encBuffer->data, encBuffer->length); - (*decBuffer)->length = encBuffer->length; + if ((*decBuffer)->length >= encBuffer->length) { + memcpy((*decBuffer)->data, encBuffer->data, encBuffer->length); + (*decBuffer)->length = encBuffer->length; + } else { + ALOGE("decBuffer size (%d) too small to hold %d bytes", + (*decBuffer)->length, encBuffer->length); + return DRM_ERROR_UNKNOWN; + } } return DRM_NO_ERROR; } status_t DrmPassthruPlugIn::onFinalizeDecryptUnit( int uniqueId, DecryptHandle* decryptHandle, int decryptUnitId) { - ALOGD("DrmPassthruPlugIn::onFinalizeDecryptUnit() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onFinalizeDecryptUnit() : %d", uniqueId); return DRM_NO_ERROR; } ssize_t DrmPassthruPlugIn::onPread(int uniqueId, DecryptHandle* decryptHandle, void* buffer, ssize_t numBytes, off64_t offset) { - ALOGD("DrmPassthruPlugIn::onPread() : %d", uniqueId); + ALOGV("DrmPassthruPlugIn::onPread() : %d", uniqueId); return 0; } diff --git a/include/media/JetPlayer.h b/include/media/JetPlayer.h index 9f6ff4ca6d07..38a3e4412b26 100644 --- a/include/media/JetPlayer.h +++ b/include/media/JetPlayer.h @@ -22,7 +22,7 @@ #include <libsonivox/jet.h> #include <libsonivox/eas_types.h> -#include "AudioTrack.h" +#include <media/AudioTrack.h> namespace android { diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp index 371428353ea8..ca93ce5b5906 100644 --- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp +++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp @@ -24,7 +24,7 @@ #include <stdlib.h> #include <string.h> #include <new> -#include <EffectBundle.h> +#include "EffectBundle.h" // effect_handle_t interface implementation for bass boost diff --git a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp index 358357e7dd38..9599dcc6f567 100755 --- a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp +++ b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp @@ -24,8 +24,9 @@ #include <stdlib.h> #include <string.h> #include <new> -#include <EffectReverb.h> -#include <LVREV.h> +#include "EffectReverb.h" +// from Reverb/lib +#include "LVREV.h" // effect_handle_t interface implementation for reverb extern "C" const struct effect_interface_s gReverbInterface; diff --git a/media/mediaserver/Android.mk b/media/mediaserver/Android.mk index 05598129646d..4e9b4cffc436 100644 --- a/media/mediaserver/Android.mk +++ b/media/mediaserver/Android.mk @@ -11,12 +11,12 @@ LOCAL_SHARED_LIBRARIES := \ libutils \ libbinder -base := $(LOCAL_PATH)/../.. - +# FIXME The duplicate audioflinger is temporary LOCAL_C_INCLUDES := \ - $(base)/services/audioflinger \ - $(base)/services/camera/libcameraservice \ - $(base)/media/libmediaplayerservice + frameworks/native/services/audioflinger \ + frameworks/base/services/audioflinger \ + frameworks/base/services/camera/libcameraservice \ + frameworks/base/media/libmediaplayerservice LOCAL_MODULE:= mediaserver diff --git a/media/mediaserver/main_mediaserver.cpp b/media/mediaserver/main_mediaserver.cpp index 1520c01bc2a9..6b1abb1b4271 100644 --- a/media/mediaserver/main_mediaserver.cpp +++ b/media/mediaserver/main_mediaserver.cpp @@ -16,16 +16,18 @@ */ #define LOG_TAG "mediaserver" +//#define LOG_NDEBUG 0 #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <binder/IServiceManager.h> #include <utils/Log.h> -#include <AudioFlinger.h> -#include <CameraService.h> -#include <MediaPlayerService.h> -#include <AudioPolicyService.h> +// from LOCAL_C_INCLUDES +#include "AudioFlinger.h" +#include "CameraService.h" +#include "MediaPlayerService.h" +#include "AudioPolicyService.h" using namespace android; diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index e0a0d2df6643..a1f73160a7cd 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -17,8 +17,8 @@ package com.android.internal.policy.impl; import android.app.Activity; +import android.app.ActivityManager; import android.app.ActivityManagerNative; -import android.app.IActivityManager; import android.app.IUiModeManager; import android.app.ProgressDialog; import android.app.UiModeManager; @@ -3832,10 +3832,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { int result = ActivityManagerNative.getDefault() .startActivity(null, dock, dock.resolveTypeIfNeeded(mContext.getContentResolver()), - null, 0, null, null, 0, true /* onlyIfNeeded*/, - false /* debug */, false /* openglTrace */, - null, null, false); - if (result == IActivityManager.START_RETURN_INTENT_TO_CALLER) { + null, null, 0, + ActivityManager.START_FLAG_ONLY_IF_NEEDED, + null, null, null); + if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) { return false; } } @@ -3843,10 +3843,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { int result = ActivityManagerNative.getDefault() .startActivity(null, mHomeIntent, mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()), - null, 0, null, null, 0, true /* onlyIfNeeded*/, - false /* debug */, false /* openglTrace */, - null, null, false); - if (result == IActivityManager.START_RETURN_INTENT_TO_CALLER) { + null, null, 0, + ActivityManager.START_FLAG_ONLY_IF_NEEDED, + null, null, null); + if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) { return false; } } catch (RemoteException ex) { diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp index bfa4a49d6e7e..1d4f12b404f7 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -37,8 +37,12 @@ #include <cutils/properties.h> #include <cutils/compiler.h> +#undef ADD_BATTERY_DATA + +#ifdef ADD_BATTERY_DATA #include <media/IMediaPlayerService.h> #include <media/IMediaDeathNotifier.h> +#endif #include <private/media/AudioTrackShared.h> #include <private/media/AudioEffectShared.h> @@ -105,6 +109,7 @@ nsecs_t AudioFlinger::mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs; // ---------------------------------------------------------------------------- +#ifdef ADD_BATTERY_DATA // To collect the amplifier usage static void addBatteryData(uint32_t params) { sp<IMediaPlayerService> service = IMediaDeathNotifier::getMediaPlayerService(); @@ -115,6 +120,7 @@ static void addBatteryData(uint32_t params) { service->addBatteryData(params); } +#endif static int load_audio_interface(const char *if_name, const hw_module_t **mod, audio_hw_device_t **dev) @@ -2612,6 +2618,7 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() } } if (param.getInt(String8(AudioParameter::keyRouting), value) == NO_ERROR) { +#ifdef ADD_BATTERY_DATA // when changing the audio output device, call addBatteryData to notify // the change if ((int)mDevice != value) { @@ -2632,6 +2639,7 @@ bool AudioFlinger::MixerThread::checkForNewParameters_l() addBatteryData(params); } } +#endif // forward device change to effects that have requested to be // aware of attached audio device. @@ -3454,8 +3462,10 @@ void AudioFlinger::PlaybackThread::Track::destroy() if (mState == ACTIVE || mState == RESUMING) { AudioSystem::stopOutput(thread->id(), mStreamType, mSessionId); +#ifdef ADD_BATTERY_DATA // to track the speaker usage addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop); +#endif } AudioSystem::releaseOutput(thread->id()); } @@ -3572,10 +3582,12 @@ status_t AudioFlinger::PlaybackThread::Track::start(pid_t tid) status = AudioSystem::startOutput(thread->id(), mStreamType, mSessionId); thread->mLock.lock(); +#ifdef ADD_BATTERY_DATA // to track the speaker usage if (status == NO_ERROR) { addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStart); } +#endif } if (status == NO_ERROR) { PlaybackThread *playbackThread = (PlaybackThread *)thread.get(); @@ -3610,8 +3622,10 @@ void AudioFlinger::PlaybackThread::Track::stop() AudioSystem::stopOutput(thread->id(), mStreamType, mSessionId); thread->mLock.lock(); +#ifdef ADD_BATTERY_DATA // to track the speaker usage addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop); +#endif } } } @@ -3630,8 +3644,10 @@ void AudioFlinger::PlaybackThread::Track::pause() AudioSystem::stopOutput(thread->id(), mStreamType, mSessionId); thread->mLock.lock(); +#ifdef ADD_BATTERY_DATA // to track the speaker usage addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop); +#endif } } } diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index c5c2901745e4..84daeaddb3ce 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -189,8 +189,8 @@ class UiModeManagerService extends IUiModeManager.Stub { } try { ActivityManagerNative.getDefault().startActivityWithConfig( - null, homeIntent, null, null, 0, null, null, 0, false, false, - newConfig); + null, homeIntent, null, null, null, 0, 0, + newConfig, null); mHoldingConfiguration = false; } catch (RemoteException e) { Slog.w(TAG, e.getCause()); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 94b9e91dd678..6f89f6eab6b3 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -286,9 +286,7 @@ public final class ActivityManagerService extends ActivityManagerNative static class PendingActivityLaunch { ActivityRecord r; ActivityRecord sourceRecord; - Uri[] grantedUriPermissions; - int grantedMode; - boolean onlyIfNeeded; + int startFlags; } final ArrayList<PendingActivityLaunch> mPendingActivityLaunches @@ -2095,8 +2093,8 @@ public final class ActivityManagerService extends ActivityManagerNative aInfo.applicationInfo.uid); if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo, - null, null, 0, 0, 0, false, false, null); + mMainStack.startActivityLocked(null, intent, null, aInfo, + null, null, 0, 0, 0, 0, null, false, null); } } @@ -2150,8 +2148,8 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); - mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo, - null, null, 0, 0, 0, false, false, null); + mMainStack.startActivityLocked(null, intent, null, ri.activityInfo, + null, null, 0, 0, 0, 0, null, false, null); } } } @@ -2257,18 +2255,15 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<N; i++) { PendingActivityLaunch pal = mPendingActivityLaunches.get(i); mMainStack.startActivityUncheckedLocked(pal.r, pal.sourceRecord, - pal.grantedUriPermissions, pal.grantedMode, pal.onlyIfNeeded, - doResume && i == (N-1)); + pal.startFlags, doResume && i == (N-1)); } mPendingActivityLaunches.clear(); } public final int startActivity(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, - boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler) { + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, + String profileFile, ParcelFileDescriptor profileFd, Bundle options) { enforceNotIsolatedCaller("startActivity"); int userId = 0; if (intent.getCategories() != null && intent.getCategories().contains(Intent.CATEGORY_HOME)) { @@ -2285,43 +2280,38 @@ public final class ActivityManagerService extends ActivityManagerNative } } return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, - debug, openglTrace, profileFile, profileFd, autoStopProfiler, null, null, userId); + resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, + null, null, options, userId); } public final WaitResult startActivityAndWait(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, - boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler) { + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, Bundle options) { enforceNotIsolatedCaller("startActivityAndWait"); WaitResult res = new WaitResult(); int userId = Binder.getOrigCallingUser(); mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, debug, openglTrace, profileFile, profileFd, autoStopProfiler, - res, null, userId); + resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, + res, null, options, userId); return res; } public final int startActivityWithConfig(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, Configuration config) { + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, Configuration config, + Bundle options) { enforceNotIsolatedCaller("startActivityWithConfig"); int ret = mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, - grantedUriPermissions, grantedMode, resultTo, resultWho, - requestCode, onlyIfNeeded, - debug, false, null, null, false, null, config, Binder.getOrigCallingUser()); + resultTo, resultWho, requestCode, startFlags, + null, null, null, config, options, Binder.getOrigCallingUser()); return ret; } public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, - int flagsMask, int flagsValues) { + int flagsMask, int flagsValues, Bundle options) { enforceNotIsolatedCaller("startActivityIntentSender"); // Refuse possible leaked file descriptors if (fillInIntent != null && fillInIntent.hasFileDescriptors()) { @@ -2344,13 +2334,13 @@ public final class ActivityManagerService extends ActivityManagerNative mAppSwitchesAllowedTime = 0; } } - int ret = pir.sendInner(0, fillInIntent, resolvedType, null, - null, resultTo, resultWho, requestCode, flagsMask, flagsValues); + int ret = pir.sendInner(0, fillInIntent, resolvedType, null, null, + resultTo, resultWho, requestCode, flagsMask, flagsValues, options); return ret; } public boolean startNextMatchingActivity(IBinder callingActivity, - Intent intent) { + Intent intent, Bundle options) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -2430,13 +2420,13 @@ public final class ActivityManagerService extends ActivityManagerNative // XXX we are not dealing with propagating grantedUriPermissions... // those are not yet exposed to user code, so there is no need. int res = mMainStack.startActivityLocked(r.app.thread, intent, - r.resolvedType, null, 0, aInfo, - resultTo != null ? resultTo.appToken : null, resultWho, - requestCode, -1, r.launchedFromUid, false, false, null); + r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null, + resultWho, requestCode, -1, r.launchedFromUid, 0, + options, false, null); Binder.restoreCallingIdentity(origId); r.finishing = wasFinishing; - if (res != START_SUCCESS) { + if (res != ActivityManager.START_SUCCESS) { return false; } return true; @@ -2445,7 +2435,7 @@ public final class ActivityManagerService extends ActivityManagerNative public final int startActivityInPackage(int uid, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded) { + String resultWho, int requestCode, int startFlags, Bundle options) { // This is so super not safe, that only the system (or okay root) // can do it. @@ -2457,21 +2447,22 @@ public final class ActivityManagerService extends ActivityManagerNative } int ret = mMainStack.startActivityMayWait(null, uid, intent, resolvedType, - null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, false, - null, null, false, null, null, userId); + resultTo, resultWho, requestCode, startFlags, + null, null, null, null, options, userId); return ret; } public final int startActivities(IApplicationThread caller, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options) { enforceNotIsolatedCaller("startActivities"); int ret = mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo, - Binder.getOrigCallingUser()); + options, Binder.getOrigCallingUser()); return ret; } public final int startActivitiesInPackage(int uid, - Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options) { // This is so super not safe, that only the system (or okay root) // can do it. @@ -2481,7 +2472,7 @@ public final class ActivityManagerService extends ActivityManagerNative "startActivityInPackage only available to the system"); } int ret = mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo, - UserId.getUserId(uid)); + options, UserId.getUserId(uid)); return ret; } @@ -4193,7 +4184,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } - if (type == INTENT_SENDER_BROADCAST && + if (type == ActivityManager.INTENT_SENDER_BROADCAST && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { throw new IllegalArgumentException( "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); @@ -4242,7 +4233,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_MU) Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid); ActivityRecord activity = null; - if (type == INTENT_SENDER_ACTIVITY_RESULT) { + if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { activity = mMainStack.isInStackLocked(token); if (activity == null) { return null; @@ -4289,7 +4280,7 @@ public final class ActivityManagerService extends ActivityManagerNative } rec = new PendingIntentRecord(this, key, callingUid); mIntentSenderRecords.put(key, rec.ref); - if (type == INTENT_SENDER_ACTIVITY_RESULT) { + if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { if (activity.pendingResults == null) { activity.pendingResults = new HashSet<WeakReference<PendingIntentRecord>>(); @@ -12454,7 +12445,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } catch (RemoteException e) { Slog.w(TAG, "Remote exception", e); - return BROADCAST_SUCCESS; + return ActivityManager.BROADCAST_SUCCESS; } } @@ -12472,7 +12463,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (requiredPermission != null) { Slog.w(TAG, "Can't broadcast sticky intent " + intent + " and enforce permission " + requiredPermission); - return BROADCAST_STICKY_CANT_HAVE_PERMISSION; + return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION; } if (intent.getComponent() != null) { throw new SecurityException( @@ -12646,7 +12637,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - return BROADCAST_SUCCESS; + return ActivityManager.BROADCAST_SUCCESS; } final Intent verifyBroadcastLocked(Intent intent) { diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 64d52ed0e583..edebbac9747b 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -25,15 +25,6 @@ import android.app.ActivityManager; import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IThumbnailRetriever; -import static android.app.IActivityManager.START_CLASS_NOT_FOUND; -import static android.app.IActivityManager.START_DELIVERED_TO_TOP; -import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; -import static android.app.IActivityManager.START_INTENT_NOT_RESOLVED; -import static android.app.IActivityManager.START_PERMISSION_DENIED; -import static android.app.IActivityManager.START_RETURN_INTENT_TO_CALLER; -import static android.app.IActivityManager.START_SUCCESS; -import static android.app.IActivityManager.START_SWITCHES_CANCELED; -import static android.app.IActivityManager.START_TASK_TO_FRONT; import android.app.IApplicationThread; import android.app.PendingIntent; import android.app.ResultInfo; @@ -2270,14 +2261,12 @@ final class ActivityStack { } final int startActivityLocked(IApplicationThread caller, - Intent intent, String resolvedType, - Uri[] grantedUriPermissions, - int grantedMode, ActivityInfo aInfo, IBinder resultTo, + Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo, String resultWho, int requestCode, - int callingPid, int callingUid, boolean onlyIfNeeded, + int callingPid, int callingUid, int startFlags, Bundle options, boolean componentSpecified, ActivityRecord[] outActivity) { - int err = START_SUCCESS; + int err = ActivityManager.START_SUCCESS; ProcessRecord callerApp = null; if (caller != null) { @@ -2289,11 +2278,11 @@ final class ActivityStack { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + callingPid + ") when starting: " + intent.toString()); - err = START_PERMISSION_DENIED; + err = ActivityManager.START_PERMISSION_DENIED; } } - if (err == START_SUCCESS) { + if (err == ActivityManager.START_SUCCESS) { Slog.i(TAG, "START {" + intent.toShortString(true, true, true, false) + "} from pid " + (callerApp != null ? callerApp.pid : callingPid)); } @@ -2319,7 +2308,7 @@ final class ActivityStack { // Transfer the result target from the source activity to the new // one being started, including any failures. if (requestCode >= 0) { - return START_FORWARD_AND_REQUEST_CONFLICT; + return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; } resultRecord = sourceRecord.resultTo; resultWho = sourceRecord.resultWho; @@ -2331,19 +2320,19 @@ final class ActivityStack { } } - if (err == START_SUCCESS && intent.getComponent() == null) { + if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { // We couldn't find a class that can handle the given Intent. // That's the end of that! - err = START_INTENT_NOT_RESOLVED; + err = ActivityManager.START_INTENT_NOT_RESOLVED; } - if (err == START_SUCCESS && aInfo == null) { + if (err == ActivityManager.START_SUCCESS && aInfo == null) { // We couldn't find the specific class specified in the Intent. // Also the end of the line. - err = START_CLASS_NOT_FOUND; + err = ActivityManager.START_CLASS_NOT_FOUND; } - if (err != START_SUCCESS) { + if (err != ActivityManager.START_SUCCESS) { if (resultRecord != null) { sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, @@ -2400,7 +2389,7 @@ final class ActivityStack { // We pretend to the caller that it was really started, but // they will just get a cancel result. mDismissKeyguardOnNextActivity = false; - return START_SUCCESS; + return ActivityManager.START_SUCCESS; } } } @@ -2419,12 +2408,10 @@ final class ActivityStack { PendingActivityLaunch pal = new PendingActivityLaunch(); pal.r = r; pal.sourceRecord = sourceRecord; - pal.grantedUriPermissions = grantedUriPermissions; - pal.grantedMode = grantedMode; - pal.onlyIfNeeded = onlyIfNeeded; + pal.startFlags = startFlags; mService.mPendingActivityLaunches.add(pal); mDismissKeyguardOnNextActivity = false; - return START_SWITCHES_CANCELED; + return ActivityManager.START_SWITCHES_CANCELED; } } @@ -2443,7 +2430,7 @@ final class ActivityStack { } err = startActivityUncheckedLocked(r, sourceRecord, - grantedUriPermissions, grantedMode, onlyIfNeeded, true); + startFlags, true); if (mDismissKeyguardOnNextActivity && mPausingActivity == null) { // Someone asked to have the keyguard dismissed on the next // activity start, but we are not actually doing an activity @@ -2466,8 +2453,7 @@ final class ActivityStack { } final int startActivityUncheckedLocked(ActivityRecord r, - ActivityRecord sourceRecord, Uri[] grantedUriPermissions, - int grantedMode, boolean onlyIfNeeded, boolean doResume) { + ActivityRecord sourceRecord, int startFlags, boolean doResume) { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; final int userId = r.userId; @@ -2494,14 +2480,14 @@ final class ActivityStack { // being launched is the same as the one making the call... or, as // a special case, if we do not know the caller then we count the // current top activity as the caller. - if (onlyIfNeeded) { + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { ActivityRecord checkedCaller = sourceRecord; if (checkedCaller == null) { checkedCaller = topRunningNonDelayedActivityLocked(notTop); } if (!checkedCaller.realActivity.equals(r.realActivity)) { // Caller is not the same as launcher, so always needed. - onlyIfNeeded = false; + startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; } } @@ -2586,7 +2572,7 @@ final class ActivityStack { if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { taskTop = resetTaskIfNeededLocked(taskTop, r); } - if (onlyIfNeeded) { + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { // We don't need to start a new activity, and // the client said not to do anything if that // is the case, so this is it! And for paranoia, make @@ -2594,7 +2580,7 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - return START_RETURN_INTENT_TO_CALLER; + return ActivityManager.START_RETURN_INTENT_TO_CALLER; } if ((launchFlags & (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) @@ -2681,7 +2667,7 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - return START_TASK_TO_FRONT; + return ActivityManager.START_TASK_TO_FRONT; } } } @@ -2710,14 +2696,14 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - if (onlyIfNeeded) { + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { // We don't need to start a new activity, and // the client said not to do anything if that // is the case, so this is it! - return START_RETURN_INTENT_TO_CALLER; + return ActivityManager.START_RETURN_INTENT_TO_CALLER; } top.deliverNewIntentLocked(callingUid, r.intent); - return START_DELIVERED_TO_TOP; + return ActivityManager.START_DELIVERED_TO_TOP; } } } @@ -2729,7 +2715,7 @@ final class ActivityStack { r.resultTo, r.resultWho, r.requestCode, Activity.RESULT_CANCELED, null); } - return START_CLASS_NOT_FOUND; + return ActivityManager.START_CLASS_NOT_FOUND; } boolean newTask = false; @@ -2770,7 +2756,7 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - return START_DELIVERED_TO_TOP; + return ActivityManager.START_DELIVERED_TO_TOP; } } else if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { @@ -2785,7 +2771,7 @@ final class ActivityStack { if (doResume) { resumeTopActivityLocked(null); } - return START_DELIVERED_TO_TOP; + return ActivityManager.START_DELIVERED_TO_TOP; } } // An existing activity is starting this new activity, so we want @@ -2809,13 +2795,6 @@ final class ActivityStack { + " in new guessed " + r.task); } - if (grantedUriPermissions != null && callingUid > 0) { - for (int i=0; i<grantedUriPermissions.length; i++) { - mService.grantUriPermissionLocked(callingUid, r.packageName, - grantedUriPermissions[i], grantedMode, r.getUriPermissionsLocked()); - } - } - mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, intent, r.getUriPermissionsLocked()); @@ -2824,12 +2803,11 @@ final class ActivityStack { } logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); startActivityLocked(r, newTask, doResume, keepCurTransition); - return START_SUCCESS; + return ActivityManager.START_SUCCESS; } - ActivityInfo resolveActivity(Intent intent, String resolvedType, boolean debug, - boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler) { + ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags, + String profileFile, ParcelFileDescriptor profileFd) { // Collect information about the target of the Intent. ActivityInfo aInfo; try { @@ -2852,13 +2830,13 @@ final class ActivityStack { aInfo.applicationInfo.packageName, aInfo.name)); // Don't debug things in the system process - if (debug) { + if ((startFlags&ActivityManager.START_FLAG_DEBUG) != 0) { if (!aInfo.processName.equals("system")) { mService.setDebugApp(aInfo.processName, true, false); } } - if (openglTrace) { + if ((startFlags&ActivityManager.START_FLAG_OPENGL_TRACES) != 0) { if (!aInfo.processName.equals("system")) { mService.setOpenGlTraceApp(aInfo.applicationInfo, aInfo.processName); } @@ -2867,7 +2845,8 @@ final class ActivityStack { if (profileFile != null) { if (!aInfo.processName.equals("system")) { mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, - profileFile, profileFd, autoStopProfiler); + profileFile, profileFd, + (startFlags&ActivityManager.START_FLAG_AUTO_STOP_PROFILER) != 0); } } } @@ -2875,12 +2854,10 @@ final class ActivityStack { } final int startActivityMayWait(IApplicationThread caller, int callingUid, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd, - boolean autoStopProfiler, - WaitResult outResult, Configuration config, int userId) { + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, + Bundle options, int userId) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -2891,8 +2868,8 @@ final class ActivityStack { intent = new Intent(intent); // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug, openglTrace, - profileFile, profileFd, autoStopProfiler); + ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, + profileFile, profileFd); aInfo = mService.getActivityInfoForUser(aInfo, userId); synchronized (mService) { @@ -2932,12 +2909,12 @@ final class ActivityStack { Slog.w(TAG, "Unable to find app for caller " + caller + " (pid=" + realCallingPid + ") when starting: " + intent.toString()); - return START_PERMISSION_DENIED; + return ActivityManager.START_PERMISSION_DENIED; } } IIntentSender target = mService.getIntentSenderLocked( - IActivityManager.INTENT_SENDER_ACTIVITY, "android", + ActivityManager.INTENT_SENDER_ACTIVITY, "android", realCallingUid, null, null, 0, new Intent[] { intent }, new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); @@ -2983,9 +2960,8 @@ final class ActivityStack { } int res = startActivityLocked(caller, intent, resolvedType, - grantedUriPermissions, grantedMode, aInfo, - resultTo, resultWho, requestCode, callingPid, callingUid, - onlyIfNeeded, componentSpecified, null); + aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, + startFlags, options, componentSpecified, null); if (mConfigWillChange && mMainStack) { // If the caller also wants to switch to a new configuration, @@ -3004,7 +2980,7 @@ final class ActivityStack { if (outResult != null) { outResult.result = res; - if (res == IActivityManager.START_SUCCESS) { + if (res == ActivityManager.START_SUCCESS) { mWaitingActivityLaunched.add(outResult); do { try { @@ -3012,7 +2988,7 @@ final class ActivityStack { } catch (InterruptedException e) { } } while (!outResult.timeout && outResult.who == null); - } else if (res == IActivityManager.START_TASK_TO_FRONT) { + } else if (res == ActivityManager.START_TASK_TO_FRONT) { ActivityRecord r = this.topRunningActivityLocked(null); if (r.nowVisible) { outResult.timeout = false; @@ -3037,8 +3013,8 @@ final class ActivityStack { } final int startActivities(IApplicationThread caller, int callingUid, - Intent[] intents, - String[] resolvedTypes, IBinder resultTo, int userId) { + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options, int userId) { if (intents == null) { throw new NullPointerException("intents is null"); } @@ -3081,8 +3057,8 @@ final class ActivityStack { intent = new Intent(intent); // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false, false, - null, null, false); + ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], + 0, null, null); // TODO: New, check if this is correct aInfo = mService.getActivityInfoForUser(aInfo, userId); @@ -3093,8 +3069,8 @@ final class ActivityStack { } int res = startActivityLocked(caller, intent, resolvedTypes[i], - null, 0, aInfo, resultTo, null, -1, callingPid, callingUid, - false, componentSpecified, outActivity); + aInfo, resultTo, null, -1, callingPid, callingUid, + 0, options, componentSpecified, outActivity); if (res < 0) { return res; } @@ -3106,7 +3082,7 @@ final class ActivityStack { Binder.restoreCallingIdentity(origId); } - return IActivityManager.START_SUCCESS; + return ActivityManager.START_SUCCESS; } void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index 0043874e18e6..9676084d3455 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -16,12 +16,13 @@ package com.android.server.am; -import android.app.IActivityManager; +import android.app.ActivityManager; import android.content.IIntentSender; import android.content.IIntentReceiver; import android.app.PendingIntent; import android.content.Intent; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.UserId; @@ -158,13 +159,13 @@ class PendingIntentRecord extends IIntentSender.Stub { String typeName() { switch (type) { - case IActivityManager.INTENT_SENDER_ACTIVITY: + case ActivityManager.INTENT_SENDER_ACTIVITY: return "startActivity"; - case IActivityManager.INTENT_SENDER_BROADCAST: + case ActivityManager.INTENT_SENDER_BROADCAST: return "broadcastIntent"; - case IActivityManager.INTENT_SENDER_SERVICE: + case ActivityManager.INTENT_SENDER_SERVICE: return "startService"; - case IActivityManager.INTENT_SENDER_ACTIVITY_RESULT: + case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT: return "activityResult"; } return Integer.toString(type); @@ -181,13 +182,13 @@ class PendingIntentRecord extends IIntentSender.Stub { public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission) { return sendInner(code, intent, resolvedType, finishedReceiver, - requiredPermission, null, null, 0, 0, 0); + requiredPermission, null, null, 0, 0, 0, null); } int sendInner(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, String resultWho, int requestCode, - int flagsMask, int flagsValues) { + int flagsMask, int flagsValues, Bundle options) { synchronized(owner) { if (!canceled) { sent = true; @@ -213,7 +214,7 @@ class PendingIntentRecord extends IIntentSender.Stub { boolean sendFinish = finishedReceiver != null; switch (key.type) { - case IActivityManager.INTENT_SENDER_ACTIVITY: + case ActivityManager.INTENT_SENDER_ACTIVITY: try { if (key.allIntents != null && key.allIntents.length > 1) { Intent[] allIntents = new Intent[key.allIntents.length]; @@ -227,22 +228,22 @@ class PendingIntentRecord extends IIntentSender.Stub { allIntents[allIntents.length-1] = finalIntent; allResolvedTypes[allResolvedTypes.length-1] = resolvedType; owner.startActivitiesInPackage(uid, allIntents, - allResolvedTypes, resultTo); + allResolvedTypes, resultTo, options); } else { owner.startActivityInPackage(uid, finalIntent, resolvedType, - resultTo, resultWho, requestCode, false); + resultTo, resultWho, requestCode, 0, options); } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); } break; - case IActivityManager.INTENT_SENDER_ACTIVITY_RESULT: + case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT: key.activity.stack.sendActivityResultLocked(-1, key.activity, key.who, key.requestCode, code, finalIntent); break; - case IActivityManager.INTENT_SENDER_BROADCAST: + case ActivityManager.INTENT_SENDER_BROADCAST: try { // If a completion callback has been requested, require // that the broadcast be delivered synchronously @@ -257,7 +258,7 @@ class PendingIntentRecord extends IIntentSender.Stub { "Unable to send startActivity intent", e); } break; - case IActivityManager.INTENT_SENDER_SERVICE: + case ActivityManager.INTENT_SENDER_SERVICE: try { owner.startServiceInPackage(uid, finalIntent, resolvedType); @@ -281,7 +282,7 @@ class PendingIntentRecord extends IIntentSender.Stub { return 0; } } - return IActivityManager.START_CANCELED; + return ActivityManager.START_CANCELED; } protected void finalize() throws Throwable { diff --git a/telephony/java/android/telephony/SmsCbCmasInfo.java b/telephony/java/android/telephony/SmsCbCmasInfo.java new file mode 100644 index 000000000000..7a89d94ab2ef --- /dev/null +++ b/telephony/java/android/telephony/SmsCbCmasInfo.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}. + * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and + * 3GPP TS 23.041 (for GSM/UMTS). + * + * {@hide} + */ +public class SmsCbCmasInfo implements Parcelable { + + // CMAS message class (in GSM/UMTS message identifier or CDMA service category). + + /** Presidential-level alert (Korean Public Alert System Class 0 message). */ + public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00; + + /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */ + public static final int CMAS_CLASS_EXTREME_THREAT = 0x01; + + /** Severe threat to life and property (Korean Public Alert System Class 1 message). */ + public static final int CMAS_CLASS_SEVERE_THREAT = 0x02; + + /** Child abduction emergency (AMBER Alert). */ + public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03; + + /** CMAS test message. */ + public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04; + + /** CMAS exercise. */ + public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05; + + /** CMAS category for operator defined use. */ + public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06; + + /** CMAS category for warning types that are reserved for future extension. */ + public static final int CMAS_CLASS_UNKNOWN = -1; + + // CMAS alert category (in CDMA type 1 elements record). + + /** CMAS alert category: Geophysical including landslide. */ + public static final int CMAS_CATEGORY_GEO = 0x00; + + /** CMAS alert category: Meteorological including flood. */ + public static final int CMAS_CATEGORY_MET = 0x01; + + /** CMAS alert category: General emergency and public safety. */ + public static final int CMAS_CATEGORY_SAFETY = 0x02; + + /** CMAS alert category: Law enforcement, military, homeland/local/private security. */ + public static final int CMAS_CATEGORY_SECURITY = 0x03; + + /** CMAS alert category: Rescue and recovery. */ + public static final int CMAS_CATEGORY_RESCUE = 0x04; + + /** CMAS alert category: Fire suppression and rescue. */ + public static final int CMAS_CATEGORY_FIRE = 0x05; + + /** CMAS alert category: Medical and public health. */ + public static final int CMAS_CATEGORY_HEALTH = 0x06; + + /** CMAS alert category: Pollution and other environmental. */ + public static final int CMAS_CATEGORY_ENV = 0x07; + + /** CMAS alert category: Public and private transportation. */ + public static final int CMAS_CATEGORY_TRANSPORT = 0x08; + + /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */ + public static final int CMAS_CATEGORY_INFRA = 0x09; + + /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */ + public static final int CMAS_CATEGORY_CBRNE = 0x0a; + + /** CMAS alert category: Other events. */ + public static final int CMAS_CATEGORY_OTHER = 0x0b; + + /** + * CMAS alert category is unknown. The category is only available for CDMA broadcasts + * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown. + */ + public static final int CMAS_CATEGORY_UNKNOWN = -1; + + // CMAS response type (in CDMA type 1 elements record). + + /** CMAS response type: Take shelter in place. */ + public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00; + + /** CMAS response type: Evacuate (Relocate). */ + public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01; + + /** CMAS response type: Make preparations. */ + public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02; + + /** CMAS response type: Execute a pre-planned activity. */ + public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03; + + /** CMAS response type: Attend to information sources. */ + public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04; + + /** CMAS response type: Avoid hazard. */ + public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05; + + /** CMAS response type: Evaluate the information in this message (not for public warnings). */ + public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06; + + /** CMAS response type: No action recommended. */ + public static final int CMAS_RESPONSE_TYPE_NONE = 0x07; + + /** + * CMAS response type is unknown. The response type is only available for CDMA broadcasts + * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown. + */ + public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1; + + // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record). + + /** CMAS severity type: Extraordinary threat to life or property. */ + public static final int CMAS_SEVERITY_EXTREME = 0x0; + + /** CMAS severity type: Significant threat to life or property. */ + public static final int CMAS_SEVERITY_SEVERE = 0x1; + + /** + * CMAS alert severity is unknown. The severity is available for CDMA warning alerts + * containing a type 1 elements record and for all GSM and UMTS alerts except for the + * Presidential-level alert class (Korean Public Alert System Class 0). + */ + public static final int CMAS_SEVERITY_UNKNOWN = -1; + + // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record). + + /** CMAS urgency type: Responsive action should be taken immediately. */ + public static final int CMAS_URGENCY_IMMEDIATE = 0x0; + + /** CMAS urgency type: Responsive action should be taken within the next hour. */ + public static final int CMAS_URGENCY_EXPECTED = 0x1; + + /** + * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts + * containing a type 1 elements record and for all GSM and UMTS alerts except for the + * Presidential-level alert class (Korean Public Alert System Class 0). + */ + public static final int CMAS_URGENCY_UNKNOWN = -1; + + // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record). + + /** CMAS certainty type: Determined to have occurred or to be ongoing. */ + public static final int CMAS_CERTAINTY_OBSERVED = 0x0; + + /** CMAS certainty type: Likely (probability > ~50%). */ + public static final int CMAS_CERTAINTY_LIKELY = 0x1; + + /** + * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts + * containing a type 1 elements record and for all GSM and UMTS alerts except for the + * Presidential-level alert class (Korean Public Alert System Class 0). + */ + public static final int CMAS_CERTAINTY_UNKNOWN = -1; + + /** CMAS message class. */ + private final int mMessageClass; + + /** CMAS category. */ + private final int mCategory; + + /** CMAS response type. */ + private final int mResponseType; + + /** CMAS severity. */ + private final int mSeverity; + + /** CMAS urgency. */ + private final int mUrgency; + + /** CMAS certainty. */ + private final int mCertainty; + + /** Create a new SmsCbCmasInfo object with the specified values. */ + public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity, + int urgency, int certainty) { + mMessageClass = messageClass; + mCategory = category; + mResponseType = responseType; + mSeverity = severity; + mUrgency = urgency; + mCertainty = certainty; + } + + /** Create a new SmsCbCmasInfo object from a Parcel. */ + SmsCbCmasInfo(Parcel in) { + mMessageClass = in.readInt(); + mCategory = in.readInt(); + mResponseType = in.readInt(); + mSeverity = in.readInt(); + mUrgency = in.readInt(); + mCertainty = in.readInt(); + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMessageClass); + dest.writeInt(mCategory); + dest.writeInt(mResponseType); + dest.writeInt(mSeverity); + dest.writeInt(mUrgency); + dest.writeInt(mCertainty); + } + + /** + * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}. + * @return one of the {@code CMAS_CLASS} values + */ + public int getMessageClass() { + return mMessageClass; + } + + /** + * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}. + * @return one of the {@code CMAS_CATEGORY} values + */ + public int getCategory() { + return mCategory; + } + + /** + * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}. + * @return one of the {@code CMAS_RESPONSE_TYPE} values + */ + public int getResponseType() { + return mResponseType; + } + + /** + * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}. + * @return one of the {@code CMAS_SEVERITY} values + */ + public int getSeverity() { + return mSeverity; + } + + /** + * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}. + * @return one of the {@code CMAS_URGENCY} values + */ + public int getUrgency() { + return mUrgency; + } + + /** + * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}. + * @return one of the {@code CMAS_CERTAINTY} values + */ + public int getCertainty() { + return mCertainty; + } + + @Override + public String toString() { + return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory + + ", responseType=" + mResponseType + ", severity=" + mSeverity + + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}'; + } + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } + + /** Creator for unparcelling objects. */ + public static final Parcelable.Creator<SmsCbCmasInfo> + CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() { + public SmsCbCmasInfo createFromParcel(Parcel in) { + return new SmsCbCmasInfo(in); + } + + public SmsCbCmasInfo[] newArray(int size) { + return new SmsCbCmasInfo[size]; + } + }; +} diff --git a/telephony/java/android/telephony/SmsCbEtwsInfo.java b/telephony/java/android/telephony/SmsCbEtwsInfo.java new file mode 100644 index 000000000000..0890d528f866 --- /dev/null +++ b/telephony/java/android/telephony/SmsCbEtwsInfo.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.format.Time; + +import com.android.internal.telephony.IccUtils; + +import java.util.Arrays; + +/** + * Contains information elements for a GSM or UMTS ETWS warning notification. + * Supported values for each element are defined in 3GPP TS 23.041. + * + * {@hide} + */ +public class SmsCbEtwsInfo implements Parcelable { + + /** ETWS warning type for earthquake. */ + public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00; + + /** ETWS warning type for tsunami. */ + public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01; + + /** ETWS warning type for earthquake and tsunami. */ + public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02; + + /** ETWS warning type for test messages. */ + public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03; + + /** ETWS warning type for other emergency types. */ + public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04; + + /** Unknown ETWS warning type. */ + public static final int ETWS_WARNING_TYPE_UNKNOWN = -1; + + /** One of the ETWS warning type constants defined in this class. */ + private final int mWarningType; + + /** Whether or not to activate the emergency user alert tone and vibration. */ + private final boolean mEmergencyUserAlert; + + /** Whether or not to activate a popup alert. */ + private final boolean mActivatePopup; + + /** + * 50-byte security information (ETWS primary notification for GSM only). As of Release 10, + * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp + * and digital signature if received. Therefore it is treated as a raw byte array and + * parceled with the broadcast intent if present, but the timestamp is only computed if an + * application asks for the individual components. + */ + private final byte[] mWarningSecurityInformation; + + /** Create a new SmsCbEtwsInfo object with the specified values. */ + public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup, + byte[] warningSecurityInformation) { + mWarningType = warningType; + mEmergencyUserAlert = emergencyUserAlert; + mActivatePopup = activatePopup; + mWarningSecurityInformation = warningSecurityInformation; + } + + /** Create a new SmsCbEtwsInfo object from a Parcel. */ + SmsCbEtwsInfo(Parcel in) { + mWarningType = in.readInt(); + mEmergencyUserAlert = (in.readInt() != 0); + mActivatePopup = (in.readInt() != 0); + mWarningSecurityInformation = in.createByteArray(); + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mWarningType); + dest.writeInt(mEmergencyUserAlert ? 1 : 0); + dest.writeInt(mActivatePopup ? 1 : 0); + dest.writeByteArray(mWarningSecurityInformation); + } + + /** + * Returns the ETWS warning type. + * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE} + */ + public int getWarningType() { + return mWarningType; + } + + /** + * Returns the ETWS emergency user alert flag. + * @return true to notify terminal to activate emergency user alert; false otherwise + */ + public boolean isEmergencyUserAlert() { + return mEmergencyUserAlert; + } + + /** + * Returns the ETWS activate popup flag. + * @return true to notify terminal to activate display popup; false otherwise + */ + public boolean isPopupAlert() { + return mActivatePopup; + } + + /** + * Returns the Warning-Security-Information timestamp (GSM primary notifications only). + * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received. + * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present + */ + public long getPrimaryNotificationTimestamp() { + if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) { + return 0; + } + + int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]); + int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]); + int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]); + int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]); + int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]); + int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]); + + // For the timezone, the most significant bit of the + // least significant nibble is the sign byte + // (meaning the max range of this field is 79 quarter-hours, + // which is more than enough) + + byte tzByte = mWarningSecurityInformation[6]; + + // Mask out sign bit. + int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08))); + + timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset; + + Time time = new Time(Time.TIMEZONE_UTC); + + // We only need to support years above 2000. + time.year = year + 2000; + time.month = month - 1; + time.monthDay = day; + time.hour = hour; + time.minute = minute; + time.second = second; + + // Timezone offset is in quarter hours. + return time.toMillis(true) - (long) (timezoneOffset * 15 * 60 * 1000); + } + + /** + * Returns the digital signature (GSM primary notifications only). As of Release 10, + * 3GPP TS 23.041 states that the UE shall ignore this value if received. + * @return a byte array containing a copy of the primary notification digital signature + */ + public byte[] getPrimaryNotificationSignature() { + if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) { + return null; + } + return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50); + } + + @Override + public String toString() { + return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert=" + + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}'; + } + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } + + /** Creator for unparcelling objects. */ + public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() { + public SmsCbEtwsInfo createFromParcel(Parcel in) { + return new SmsCbEtwsInfo(in); + } + + public SmsCbEtwsInfo[] newArray(int size) { + return new SmsCbEtwsInfo[size]; + } + }; +} diff --git a/telephony/java/android/telephony/SmsCbLocation.java b/telephony/java/android/telephony/SmsCbLocation.java new file mode 100644 index 000000000000..7b5bd0d4bb71 --- /dev/null +++ b/telephony/java/android/telephony/SmsCbLocation.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.gsm.GsmCellLocation; + +/** + * Represents the location and geographical scope of a cell broadcast message. + * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast + * geographical scope is cell wide or Location Area wide. For CDMA, the + * broadcast geographical scope is always PLMN wide. + * + * @hide + */ +public class SmsCbLocation implements Parcelable { + + /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */ + private final String mPlmn; + + private final int mLac; + private final int mCid; + + /** + * Construct an empty location object. This is used for some test cases, and for + * cell broadcasts saved in older versions of the database without location info. + */ + public SmsCbLocation() { + mPlmn = ""; + mLac = -1; + mCid = -1; + } + + /** + * Construct a location object for the PLMN. This class is immutable, so + * the same object can be reused for multiple broadcasts. + */ + public SmsCbLocation(String plmn) { + mPlmn = plmn; + mLac = -1; + mCid = -1; + } + + /** + * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so + * the same object can be reused for multiple broadcasts. + */ + public SmsCbLocation(String plmn, int lac, int cid) { + mPlmn = plmn; + mLac = lac; + mCid = cid; + } + + /** + * Initialize the object from a Parcel. + */ + public SmsCbLocation(Parcel in) { + mPlmn = in.readString(); + mLac = in.readInt(); + mCid = in.readInt(); + } + + /** + * Returns the MCC/MNC of the network as a String. + * @return the PLMN identifier (MCC+MNC) as a String + */ + public String getPlmn() { + return mPlmn; + } + + /** + * Returns the GSM location area code, or UMTS service area code. + * @return location area code, -1 if unknown, 0xffff max legal value + */ + public int getLac() { + return mLac; + } + + /** + * Returns the GSM or UMTS cell ID. + * @return gsm cell id, -1 if unknown, 0xffff max legal value + */ + public int getCid() { + return mCid; + } + + @Override + public int hashCode() { + int hash = mPlmn.hashCode(); + hash = hash * 31 + mLac; + hash = hash * 31 + mCid; + return hash; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o == null || !(o instanceof SmsCbLocation)) { + return false; + } + SmsCbLocation other = (SmsCbLocation) o; + return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid; + } + + @Override + public String toString() { + return '[' + mPlmn + ',' + mLac + ',' + mCid + ']'; + } + + /** + * Test whether this location is within the location area of the specified object. + * + * @param area the location area to compare with this location + * @return true if this location is contained within the specified location area + */ + public boolean isInLocationArea(SmsCbLocation area) { + if (mCid != -1 && mCid != area.mCid) { + return false; + } + if (mLac != -1 && mLac != area.mLac) { + return false; + } + return mPlmn.equals(area.mPlmn); + } + + /** + * Test whether this location is within the location area of the CellLocation. + * + * @param plmn the PLMN to use for comparison + * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with + * @param cid the Cell ID to compare with + * @return true if this location is contained within the specified PLMN, LAC, and Cell ID + */ + public boolean isInLocationArea(String plmn, int lac, int cid) { + if (!mPlmn.equals(plmn)) { + return false; + } + + if (mLac != -1 && mLac != lac) { + return false; + } + + if (mCid != -1 && mCid != cid) { + return false; + } + + return true; + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mPlmn); + dest.writeInt(mLac); + dest.writeInt(mCid); + } + + public static final Parcelable.Creator<SmsCbLocation> CREATOR + = new Parcelable.Creator<SmsCbLocation>() { + @Override + public SmsCbLocation createFromParcel(Parcel in) { + return new SmsCbLocation(in); + } + + @Override + public SmsCbLocation[] newArray(int size) { + return new SmsCbLocation[size]; + } + }; + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } +} diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java index 383e0f9c90c4..046bf8c700eb 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -16,444 +16,367 @@ package android.telephony; -import android.text.format.Time; -import android.util.Log; - -import com.android.internal.telephony.GsmAlphabet; -import com.android.internal.telephony.IccUtils; -import com.android.internal.telephony.gsm.SmsCbHeader; - -import java.io.UnsupportedEncodingException; +import android.os.Parcel; +import android.os.Parcelable; /** - * Describes an SMS-CB message. + * Parcelable object containing a received cell broadcast message. There are four different types + * of Cell Broadcast messages: + * + * <ul> + * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li> + * <li>cell information messages, broadcast on channel 50, indicating the current cell name for + * roaming purposes (required to display on the idle screen in Brazil)</li> + * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li> + * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li> + * </ul> + * + * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only), + * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were + * unified under a common name, avoiding some names, such as "Message Identifier", that refer to + * two completely different concepts in 3GPP and CDMA. + * + * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name + * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP + * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the + * application should + * + * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used + * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is + * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit + * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number + * are considered unique to the PLMN, to the current cell, or to the current Location Area (or + * Service Area in UMTS). The relevant values are concatenated into a single String which will be + * unique if the messages are not duplicates. + * + * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the + * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object. + * + * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive + * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts. + * Only system applications such as the CellBroadcastReceiver may receive notifications for + * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or + * interference with the immediate display of the alert message and playing of the alert sound and + * vibration pattern, which could be caused by poorly written or malicious non-system code. * - * {@hide} + * @hide */ -public class SmsCbMessage { +public class SmsCbMessage implements Parcelable { - /** - * Cell wide immediate geographical scope - */ + protected static final String LOG_TAG = "SMSCB"; + + /** Cell wide geographical scope with immediate display (GSM/UMTS only). */ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0; - /** - * PLMN wide geographical scope - */ + /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */ public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1; - /** - * Location / service area wide geographical scope - */ + /** Location / service area wide geographical scope (GSM/UMTS only). */ public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2; - /** - * Cell wide geographical scope - */ + /** Cell wide geographical scope (GSM/UMTS only). */ public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3; + /** GSM or UMTS format cell broadcast. */ + public static final int MESSAGE_FORMAT_3GPP = 1; + + /** CDMA format cell broadcast. */ + public static final int MESSAGE_FORMAT_3GPP2 = 2; + + /** Normal message priority. */ + public static final int MESSAGE_PRIORITY_NORMAL = 0; + + /** Interactive message priority. */ + public static final int MESSAGE_PRIORITY_INTERACTIVE = 1; + + /** Urgent message priority. */ + public static final int MESSAGE_PRIORITY_URGENT = 2; + + /** Emergency message priority. */ + public static final int MESSAGE_PRIORITY_EMERGENCY = 3; + + /** Format of this message (for interpretation of service category values). */ + private final int mMessageFormat; + + /** Geographical scope of broadcast. */ + private final int mGeographicalScope; + /** - * Create an instance of this class from a received PDU - * - * @param pdu PDU bytes - * @return An instance of this class, or null if invalid pdu + * Serial number of broadcast (message identifier for CDMA, geographical scope + message code + + * update number for GSM/UMTS). The serial number plus the location code uniquely identify + * a cell broadcast for duplicate detection. */ - public static SmsCbMessage createFromPdu(byte[] pdu) { - try { - return new SmsCbMessage(pdu); - } catch (IllegalArgumentException e) { - Log.w(LOG_TAG, "Failed parsing SMS-CB pdu", e); - return null; - } - } - - private static final String LOG_TAG = "SMSCB"; + private final int mSerialNumber; /** - * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5. + * Location identifier for this message. It consists of the current operator MCC/MNC as a + * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the + * message is not binary 01, the Location Area is included for comparison. If the GS is + * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified. */ - private static final String[] LANGUAGE_CODES_GROUP_0 = { - "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu", - "pl", null - }; + private final SmsCbLocation mLocation; /** - * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5. + * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings, + * the information provided by the category is also available via {@link #getEtwsWarningInfo()} + * or {@link #getCmasWarningInfo()}. */ - private static final String[] LANGUAGE_CODES_GROUP_2 = { - "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null, - null, null - }; + private final int mServiceCategory; + + /** Message language, as a two-character string, e.g. "en". */ + private final String mLanguage; - private static final char CARRIAGE_RETURN = 0x0d; + /** Message body, as a String. */ + private final String mBody; - private static final int PDU_BODY_PAGE_LENGTH = 82; + /** Message priority (including emergency priority). */ + private final int mPriority; - private SmsCbHeader mHeader; + /** ETWS warning notification information (ETWS warnings only). */ + private final SmsCbEtwsInfo mEtwsWarningInfo; - private String mLanguage; + /** CMAS warning notification information (CMAS warnings only). */ + private final SmsCbCmasInfo mCmasWarningInfo; - private String mBody; + /** + * Create a new SmsCbMessage with the specified data. + */ + public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber, + SmsCbLocation location, int serviceCategory, String language, String body, + int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) { + mMessageFormat = messageFormat; + mGeographicalScope = geographicalScope; + mSerialNumber = serialNumber; + mLocation = location; + mServiceCategory = serviceCategory; + mLanguage = language; + mBody = body; + mPriority = priority; + mEtwsWarningInfo = etwsWarningInfo; + mCmasWarningInfo = cmasWarningInfo; + } - /** Timestamp of ETWS primary notification with security. */ - private long mPrimaryNotificationTimestamp; + /** Create a new SmsCbMessage object from a Parcel. */ + public SmsCbMessage(Parcel in) { + mMessageFormat = in.readInt(); + mGeographicalScope = in.readInt(); + mSerialNumber = in.readInt(); + mLocation = new SmsCbLocation(in); + mServiceCategory = in.readInt(); + mLanguage = in.readString(); + mBody = in.readString(); + mPriority = in.readInt(); + int type = in.readInt(); + switch (type) { + case 'E': + // unparcel ETWS warning information + mEtwsWarningInfo = new SmsCbEtwsInfo(in); + mCmasWarningInfo = null; + break; - /** 43 byte digital signature of ETWS primary notification with security. */ - private byte[] mPrimaryNotificationDigitalSignature; + case 'C': + // unparcel CMAS warning information + mEtwsWarningInfo = null; + mCmasWarningInfo = new SmsCbCmasInfo(in); + break; - private SmsCbMessage(byte[] pdu) throws IllegalArgumentException { - mHeader = new SmsCbHeader(pdu); - if (mHeader.format == SmsCbHeader.FORMAT_ETWS_PRIMARY) { - mBody = "ETWS"; - // ETWS primary notification with security is 56 octets in length - if (pdu.length >= SmsCbHeader.PDU_LENGTH_ETWS) { - mPrimaryNotificationTimestamp = getTimestampMillis(pdu); - mPrimaryNotificationDigitalSignature = new byte[43]; - // digital signature starts after 6 byte header and 7 byte timestamp - System.arraycopy(pdu, 13, mPrimaryNotificationDigitalSignature, 0, 43); - } - } else { - parseBody(pdu); + default: + mEtwsWarningInfo = null; + mCmasWarningInfo = null; } } /** - * Return the geographical scope of this message, one of - * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE}, - * {@link #GEOGRAPHICAL_SCOPE_PLMN_WIDE}, - * {@link #GEOGRAPHICAL_SCOPE_LA_WIDE}, - * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE} + * Flatten this object into a Parcel. * - * @return Geographical scope + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). */ - public int getGeographicalScope() { - return mHeader.geographicalScope; + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMessageFormat); + dest.writeInt(mGeographicalScope); + dest.writeInt(mSerialNumber); + mLocation.writeToParcel(dest, flags); + dest.writeInt(mServiceCategory); + dest.writeString(mLanguage); + dest.writeString(mBody); + dest.writeInt(mPriority); + if (mEtwsWarningInfo != null) { + // parcel ETWS warning information + dest.writeInt('E'); + mEtwsWarningInfo.writeToParcel(dest, flags); + } else if (mCmasWarningInfo != null) { + // parcel CMAS warning information + dest.writeInt('C'); + mCmasWarningInfo.writeToParcel(dest, flags); + } else { + // no ETWS or CMAS warning information + dest.writeInt('0'); + } } + public static final Parcelable.Creator<SmsCbMessage> CREATOR + = new Parcelable.Creator<SmsCbMessage>() { + @Override + public SmsCbMessage createFromParcel(Parcel in) { + return new SmsCbMessage(in); + } + + @Override + public SmsCbMessage[] newArray(int size) { + return new SmsCbMessage[size]; + } + }; + /** - * Get the ISO-639-1 language code for this message, or null if unspecified + * Return the geographical scope of this message (GSM/UMTS only). * - * @return Language code + * @return Geographical scope */ - public String getLanguageCode() { - return mLanguage; + public int getGeographicalScope() { + return mGeographicalScope; } /** - * Get the body of this message, or null if no body available + * Return the broadcast serial number of broadcast (message identifier for CDMA, or + * geographical scope + message code + update number for GSM/UMTS). The serial number plus + * the location code uniquely identify a cell broadcast for duplicate detection. * - * @return Body, or null + * @return the 16-bit CDMA message identifier or GSM/UMTS serial number */ - public String getMessageBody() { - return mBody; + public int getSerialNumber() { + return mSerialNumber; } /** - * Get the message identifier of this message (0-65535) + * Return the location identifier for this message, consisting of the MCC/MNC as a + * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the + * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the + * cell ID is also included. The {@link SmsCbLocation} object includes a method to test + * if the location is included within another location area or within a PLMN and CellLocation. * - * @return Message identifier + * @return the geographical location code for duplicate message detection */ - public int getMessageIdentifier() { - return mHeader.messageIdentifier; + public SmsCbLocation getLocation() { + return mLocation; } /** - * Get the message code of this message (0-1023) + * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation + * of the category is radio technology specific. For ETWS and CMAS warnings, the information + * provided by the category is available via {@link #getEtwsWarningInfo()} or + * {@link #getCmasWarningInfo()} in a radio technology independent format. * - * @return Message code + * @return the radio technology specific service category */ - public int getMessageCode() { - return mHeader.messageCode; + public int getServiceCategory() { + return mServiceCategory; } /** - * Get the update number of this message (0-15) + * Get the ISO-639-1 language code for this message, or null if unspecified * - * @return Update number + * @return Language code */ - public int getUpdateNumber() { - return mHeader.updateNumber; + public String getLanguageCode() { + return mLanguage; } /** - * Get the format of this message. - * @return {@link SmsCbHeader#FORMAT_GSM}, {@link SmsCbHeader#FORMAT_UMTS}, or - * {@link SmsCbHeader#FORMAT_ETWS_PRIMARY} + * Get the body of this message, or null if no body available + * + * @return Body, or null */ - public int getMessageFormat() { - return mHeader.format; + public String getMessageBody() { + return mBody; } /** - * For ETWS primary notifications, return the emergency user alert flag. - * @return true to notify terminal to activate emergency user alert; false otherwise + * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}). + * @return an integer representing 3GPP or 3GPP2 message format */ - public boolean getEtwsEmergencyUserAlert() { - return mHeader.etwsEmergencyUserAlert; + public int getMessageFormat() { + return mMessageFormat; } /** - * For ETWS primary notifications, return the popup flag. - * @return true to notify terminal to activate display popup; false otherwise + * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL} + * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return + * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}. + * @return an integer representing the message priority */ - public boolean getEtwsPopup() { - return mHeader.etwsPopup; + public int getMessagePriority() { + return mPriority; } /** - * For ETWS primary notifications, return the warning type. - * @return a value such as {@link SmsCbConstants#ETWS_WARNING_TYPE_EARTHQUAKE} + * If this is an ETWS warning notification then this method will return an object containing + * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an + * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte + * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the + * ETWS primary notification timestamp and digital signature if received. + * + * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification */ - public int getEtwsWarningType() { - return mHeader.etwsWarningType; + public SmsCbEtwsInfo getEtwsWarningInfo() { + return mEtwsWarningInfo; } /** - * For ETWS primary notifications, return the Warning-Security-Information timestamp. - * @return a timestamp in System.currentTimeMillis() format. + * If this is a CMAS warning notification then this method will return an object containing + * the CMAS message class, category, response type, severity, urgency and certainty. + * The message class is always present. Severity, urgency and certainty are present for CDMA + * warning notifications containing a type 1 elements record and for GSM and UMTS warnings + * except for the Presidential-level alert category. Category and response type are only + * available for CDMA notifications containing a type 1 elements record. + * + * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification */ - public long getEtwsSecurityTimestamp() { - return mPrimaryNotificationTimestamp; + public SmsCbCmasInfo getCmasWarningInfo() { + return mCmasWarningInfo; } /** - * For ETWS primary notifications, return the 43 byte digital signature. - * @return a byte array containing a copy of the digital signature + * Return whether this message is an emergency (PWS) message type. + * @return true if the message is a public warning notification; false otherwise */ - public byte[] getEtwsSecuritySignature() { - return mPrimaryNotificationDigitalSignature.clone(); + public boolean isEmergencyMessage() { + return mPriority == MESSAGE_PRIORITY_EMERGENCY; } /** - * Parse and unpack the body text according to the encoding in the DCS. - * After completing successfully this method will have assigned the body - * text into mBody, and optionally the language code into mLanguage - * - * @param pdu The pdu + * Return whether this message is an ETWS warning alert. + * @return true if the message is an ETWS warning notification; false otherwise */ - private void parseBody(byte[] pdu) { - int encoding; - boolean hasLanguageIndicator = false; - - // Extract encoding and language from DCS, as defined in 3gpp TS 23.038, - // section 5. - switch ((mHeader.dataCodingScheme & 0xf0) >> 4) { - case 0x00: - encoding = SmsMessage.ENCODING_7BIT; - mLanguage = LANGUAGE_CODES_GROUP_0[mHeader.dataCodingScheme & 0x0f]; - break; - - case 0x01: - hasLanguageIndicator = true; - if ((mHeader.dataCodingScheme & 0x0f) == 0x01) { - encoding = SmsMessage.ENCODING_16BIT; - } else { - encoding = SmsMessage.ENCODING_7BIT; - } - break; - - case 0x02: - encoding = SmsMessage.ENCODING_7BIT; - mLanguage = LANGUAGE_CODES_GROUP_2[mHeader.dataCodingScheme & 0x0f]; - break; - - case 0x03: - encoding = SmsMessage.ENCODING_7BIT; - break; - - case 0x04: - case 0x05: - switch ((mHeader.dataCodingScheme & 0x0c) >> 2) { - case 0x01: - encoding = SmsMessage.ENCODING_8BIT; - break; - - case 0x02: - encoding = SmsMessage.ENCODING_16BIT; - break; - - case 0x00: - default: - encoding = SmsMessage.ENCODING_7BIT; - break; - } - break; - - case 0x06: - case 0x07: - // Compression not supported - case 0x09: - // UDH structure not supported - case 0x0e: - // Defined by the WAP forum not supported - encoding = SmsMessage.ENCODING_UNKNOWN; - break; - - case 0x0f: - if (((mHeader.dataCodingScheme & 0x04) >> 2) == 0x01) { - encoding = SmsMessage.ENCODING_8BIT; - } else { - encoding = SmsMessage.ENCODING_7BIT; - } - break; - - default: - // Reserved values are to be treated as 7-bit - encoding = SmsMessage.ENCODING_7BIT; - break; - } - - if (mHeader.format == SmsCbHeader.FORMAT_UMTS) { - // Payload may contain multiple pages - int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; - - if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) - * nrPages) { - throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match " - + nrPages + " pages"); - } - - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < nrPages; i++) { - // Each page is 82 bytes followed by a length octet indicating - // the number of useful octets within those 82 - int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i; - int length = pdu[offset + PDU_BODY_PAGE_LENGTH]; - - if (length > PDU_BODY_PAGE_LENGTH) { - throw new IllegalArgumentException("Page length " + length - + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH); - } - - sb.append(unpackBody(pdu, encoding, offset, length, hasLanguageIndicator)); - } - mBody = sb.toString(); - } else { - // Payload is one single page - int offset = SmsCbHeader.PDU_HEADER_LENGTH; - int length = pdu.length - offset; - - mBody = unpackBody(pdu, encoding, offset, length, hasLanguageIndicator); - } + public boolean isEtwsMessage() { + return mEtwsWarningInfo != null; } /** - * Unpack body text from the pdu using the given encoding, position and - * length within the pdu - * - * @param pdu The pdu - * @param encoding The encoding, as derived from the DCS - * @param offset Position of the first byte to unpack - * @param length Number of bytes to unpack - * @param hasLanguageIndicator true if the body text is preceded by a - * language indicator. If so, this method will as a side-effect - * assign the extracted language code into mLanguage - * @return Body text + * Return whether this message is a CMAS warning alert. + * @return true if the message is a CMAS warning notification; false otherwise */ - private String unpackBody(byte[] pdu, int encoding, int offset, int length, - boolean hasLanguageIndicator) { - String body = null; - - switch (encoding) { - case SmsMessage.ENCODING_7BIT: - body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7); - - if (hasLanguageIndicator && body != null && body.length() > 2) { - // Language is two GSM characters followed by a CR. - // The actual body text is offset by 3 characters. - mLanguage = body.substring(0, 2); - body = body.substring(3); - } - break; - - case SmsMessage.ENCODING_16BIT: - if (hasLanguageIndicator && pdu.length >= offset + 2) { - // Language is two GSM characters. - // The actual body text is offset by 2 bytes. - mLanguage = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2); - offset += 2; - length -= 2; - } - - try { - body = new String(pdu, offset, (length & 0xfffe), "utf-16"); - } catch (UnsupportedEncodingException e) { - // Eeeek - } - break; - - default: - break; - } - - if (body != null) { - // Remove trailing carriage return - for (int i = body.length() - 1; i >= 0; i--) { - if (body.charAt(i) != CARRIAGE_RETURN) { - body = body.substring(0, i + 1); - break; - } - } - } else { - body = ""; - } - - return body; + public boolean isCmasMessage() { + return mCmasWarningInfo != null; } - /** - * Parses an ETWS primary notification timestamp and returns a currentTimeMillis()-style - * timestamp. Copied from com.android.internal.telephony.gsm.SmsMessage. - * @param pdu the ETWS primary notification PDU to decode - * @return the UTC timestamp from the Warning-Security-Information parameter - */ - private long getTimestampMillis(byte[] pdu) { - // Timestamp starts after CB header, in pdu[6] - int year = IccUtils.gsmBcdByteToInt(pdu[6]); - int month = IccUtils.gsmBcdByteToInt(pdu[7]); - int day = IccUtils.gsmBcdByteToInt(pdu[8]); - int hour = IccUtils.gsmBcdByteToInt(pdu[9]); - int minute = IccUtils.gsmBcdByteToInt(pdu[10]); - int second = IccUtils.gsmBcdByteToInt(pdu[11]); - - // For the timezone, the most significant bit of the - // least significant nibble is the sign byte - // (meaning the max range of this field is 79 quarter-hours, - // which is more than enough) - - byte tzByte = pdu[12]; - - // Mask out sign bit. - int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08))); - - timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset; - - Time time = new Time(Time.TIMEZONE_UTC); - - // It's 2006. Should I really support years < 2000? - time.year = year >= 90 ? year + 1900 : year + 2000; - time.month = month - 1; - time.monthDay = day; - time.hour = hour; - time.minute = minute; - time.second = second; - - // Timezone offset is in quarter hours. - return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000); + @Override + public String toString() { + return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber=" + + mSerialNumber + ", location=" + mLocation + ", serviceCategory=" + + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody + + ", priority=" + mPriority + + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "") + + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}'; } /** - * Append text to the message body. This is used to concatenate multi-page GSM broadcasts. - * @param body the text to append to this message + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects */ - public void appendToBody(String body) { - mBody = mBody + body; - } - @Override - public String toString() { - return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage + - ", body=\"" + mBody + "\"}"; + public int describeContents() { + return 0; } } diff --git a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java new file mode 100644 index 000000000000..f94efd8f56d7 --- /dev/null +++ b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.cdma; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * CDMA Service Category Program Data from SCPT teleservice SMS. + * The CellBroadcastReceiver app receives an Intent with action + * {@link android.provider.Telephony.Sms.Intents#SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION} + * containing an array of these objects to update its list of cell broadcast service categories + * to display. + * + * {@hide} + */ +public class CdmaSmsCbProgramData implements Parcelable { + + /** Delete the specified service category from the list of enabled categories. */ + public static final int OPERATION_DELETE_CATEGORY = 0; + + /** Add the specified service category to the list of enabled categories. */ + public static final int OPERATION_ADD_CATEGORY = 1; + + /** Clear all service categories from the list of enabled categories. */ + public static final int OPERATION_CLEAR_CATEGORIES = 2; + + /** Alert option: no alert. */ + public static final int ALERT_OPTION_NO_ALERT = 0; + + /** Alert option: default alert. */ + public static final int ALERT_OPTION_DEFAULT_ALERT = 1; + + /** Alert option: vibrate alert once. */ + public static final int ALERT_OPTION_VIBRATE_ONCE = 2; + + /** Alert option: vibrate alert - repeat. */ + public static final int ALERT_OPTION_VIBRATE_REPEAT = 3; + + /** Alert option: visual alert once. */ + public static final int ALERT_OPTION_VISUAL_ONCE = 4; + + /** Alert option: visual alert - repeat. */ + public static final int ALERT_OPTION_VISUAL_REPEAT = 5; + + /** Alert option: low-priority alert once. */ + public static final int ALERT_OPTION_LOW_PRIORITY_ONCE = 6; + + /** Alert option: low-priority alert - repeat. */ + public static final int ALERT_OPTION_LOW_PRIORITY_REPEAT = 7; + + /** Alert option: medium-priority alert once. */ + public static final int ALERT_OPTION_MED_PRIORITY_ONCE = 8; + + /** Alert option: medium-priority alert - repeat. */ + public static final int ALERT_OPTION_MED_PRIORITY_REPEAT = 9; + + /** Alert option: high-priority alert once. */ + public static final int ALERT_OPTION_HIGH_PRIORITY_ONCE = 10; + + /** Alert option: high-priority alert - repeat. */ + public static final int ALERT_OPTION_HIGH_PRIORITY_REPEAT = 11; + + /** Service category operation (add/delete/clear). */ + private final int mOperation; + + /** Service category to modify. */ + private final int mCategory; + + /** Language used for service category name (ISO 639 two character code). */ + private final String mLanguage; + + /** Maximum number of messages to store for this service category. */ + private final int mMaxMessages; + + /** Service category alert option. */ + private final int mAlertOption; + + /** Name of service category. */ + private final String mCategoryName; + + /** Create a new CdmaSmsCbProgramData object with the specified values. */ + public CdmaSmsCbProgramData(int operation, int category, String language, int maxMessages, + int alertOption, String categoryName) { + mOperation = operation; + mCategory = category; + mLanguage = language; + mMaxMessages = maxMessages; + mAlertOption = alertOption; + mCategoryName = categoryName; + } + + /** Create a new CdmaSmsCbProgramData object from a Parcel. */ + CdmaSmsCbProgramData(Parcel in) { + mOperation = in.readInt(); + mCategory = in.readInt(); + mLanguage = in.readString(); + mMaxMessages = in.readInt(); + mAlertOption = in.readInt(); + mCategoryName = in.readString(); + } + + /** + * Flatten this object into a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written (ignored). + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mOperation); + dest.writeInt(mCategory); + dest.writeString(mLanguage); + dest.writeInt(mMaxMessages); + dest.writeInt(mAlertOption); + dest.writeString(mCategoryName); + } + + /** + * Returns the service category operation, e.g. {@link #OPERATION_ADD_CATEGORY}. + * @return one of the {@code OPERATION_*} values + */ + public int getOperation() { + return mOperation; + } + + /** + * Returns the CDMA service category to modify. + * @return a 16-bit CDMA service category value + */ + public int getCategory() { + return mCategory; + } + + /** + * Returns the ISO-639-1 language code for the service category name, or null if not present. + * @return a two-digit ISO-639-1 language code, e.g. "en" for English + */ + public String getLanguageCode() { + return mLanguage; + } + + /** + * Returns the maximum number of messages to store for this service category. + * @return the maximum number of messages to store for this service category + */ + public int getMaxMessages() { + return mMaxMessages; + } + + /** + * Returns the service category alert option, e.g. {@link #ALERT_OPTION_DEFAULT_ALERT}. + * @return one of the {@code ALERT_OPTION_*} values + */ + public int getAlertOption() { + return mAlertOption; + } + + /** + * Returns the service category name, in the language specified by {@link #getLanguageCode()}. + * @return an optional service category name + */ + public String getCategoryName() { + return mCategoryName; + } + + @Override + public String toString() { + return "CdmaSmsCbProgramData{operation=" + mOperation + ", category=" + mCategory + + ", language=" + mLanguage + ", max messages=" + mMaxMessages + + ", alert option=" + mAlertOption + ", category name=" + mCategoryName + '}'; + } + + /** + * Describe the kinds of special objects contained in the marshalled representation. + * @return a bitmask indicating this Parcelable contains no special objects + */ + @Override + public int describeContents() { + return 0; + } + + /** Creator for unparcelling objects. */ + public static final Parcelable.Creator<CdmaSmsCbProgramData> + CREATOR = new Parcelable.Creator<CdmaSmsCbProgramData>() { + @Override + public CdmaSmsCbProgramData createFromParcel(Parcel in) { + return new CdmaSmsCbProgramData(in); + } + + @Override + public CdmaSmsCbProgramData[] newArray(int size) { + return new CdmaSmsCbProgramData[size]; + } + }; +} diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java index a42a267a1fda..d6f96ff6fe96 100644 --- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java @@ -39,6 +39,7 @@ import android.os.SystemProperties; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.provider.Settings; +import android.telephony.SmsCbMessage; import android.telephony.SmsMessage; import android.telephony.ServiceState; import android.util.Log; @@ -339,7 +340,7 @@ public abstract class SMSDispatcher extends Handler { * @param intent intent to broadcast * @param permission Receivers are required to have this permission */ - void dispatch(Intent intent, String permission) { + public void dispatch(Intent intent, String permission) { // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any // receivers time to take their own wake locks. mWakeLock.acquire(WAKE_LOCK_TIMEOUT); @@ -1078,16 +1079,16 @@ public abstract class SMSDispatcher extends Handler { } }; - protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) { - if (isEmergencyMessage) { + protected void dispatchBroadcastMessage(SmsCbMessage message) { + if (message.isEmergencyMessage()) { Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION); - intent.putExtra("pdus", pdus); - Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus"); + intent.putExtra("message", message); + Log.d(TAG, "Dispatching emergency SMS CB"); dispatch(intent, RECEIVE_EMERGENCY_BROADCAST_PERMISSION); } else { Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION); - intent.putExtra("pdus", pdus); - Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus"); + intent.putExtra("message", message); + Log.d(TAG, "Dispatching SMS CB"); dispatch(intent, RECEIVE_SMS_PERMISSION); } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index ca8d9ae11617..bfa23e7be8f3 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -30,8 +30,10 @@ import android.os.SystemProperties; import android.preference.PreferenceManager; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; +import android.telephony.SmsCbMessage; import android.telephony.SmsManager; import android.telephony.SmsMessage.MessageClass; +import android.telephony.cdma.CdmaSmsCbProgramData; import android.util.Log; import com.android.internal.telephony.CommandsInterface; @@ -50,6 +52,7 @@ import com.android.internal.util.HexDump; import java.io.ByteArrayOutputStream; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import android.content.res.Resources; @@ -97,6 +100,24 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } } + /** + * Dispatch service category program data to the CellBroadcastReceiver app, which filters + * the broadcast alerts to display. + * @param sms the SMS message containing one or more + * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects. + */ + private void handleServiceCategoryProgramData(SmsMessage sms) { + List<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData(); + if (programDataList == null) { + Log.e(TAG, "handleServiceCategoryProgramData: program data list is null!"); + return; + } + + Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION); + intent.putExtra("program_data_list", (CdmaSmsCbProgramData[]) programDataList.toArray()); + dispatch(intent, RECEIVE_SMS_PERMISSION); + } + /** {@inheritDoc} */ @Override public int dispatchMessage(SmsMessageBase smsb) { @@ -119,8 +140,19 @@ final class CdmaSMSDispatcher extends SMSDispatcher { return Intents.RESULT_SMS_HANDLED; } - // See if we have a network duplicate SMS. SmsMessage sms = (SmsMessage) smsb; + + // Handle CMAS emergency broadcast messages. + if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) { + Log.d(TAG, "Broadcast type message"); + SmsCbMessage message = sms.parseBroadcastSms(); + if (message != null) { + dispatchBroadcastMessage(message); + } + return Intents.RESULT_SMS_HANDLED; + } + + // See if we have a network duplicate SMS. mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint(); if (mLastAcknowledgedSmsFingerprint != null && Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) { @@ -149,6 +181,9 @@ final class CdmaSMSDispatcher extends SMSDispatcher { sms.isStatusReportMessage()) { handleCdmaStatusReport(sms); handled = true; + } else if (SmsEnvelope.TELESERVICE_SCPT == teleService) { + handleServiceCategoryProgramData(sms); + handled = true; } else if ((sms.getUserData() == null)) { if (false) { Log.d(TAG, "Received SMS without user data"); diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 1409cab2aa2a..a723de3543c3 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -19,7 +19,11 @@ package com.android.internal.telephony.cdma; import android.os.Parcel; import android.os.SystemProperties; import android.telephony.PhoneNumberUtils; +import android.telephony.SmsCbLocation; +import android.telephony.SmsCbMessage; +import android.telephony.cdma.CdmaSmsCbProgramData; import android.util.Log; + import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; @@ -38,6 +42,9 @@ import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; +import java.util.List; + +import static android.telephony.SmsMessage.MessageClass; /** * TODO(cleanup): these constants are disturbing... are they not just @@ -47,12 +54,6 @@ import java.io.IOException; * named CdmaSmsMessage, could it not? */ -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; -import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; -import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER; -import static android.telephony.SmsMessage.MessageClass; - /** * TODO(cleanup): internally returning null in many places makes * debugging very hard (among many other reasons) and should be made @@ -192,16 +193,17 @@ public class SmsMessage extends SmsMessageBase { // bearer data countInt = p.readInt(); //p_cur->uBearerDataLen - if (countInt >0) { - data = new byte[countInt]; - //p_cur->aBearerData[digitCount] : - for (int index=0; index < countInt; index++) { - data[index] = p.readByte(); - } - env.bearerData = data; - // BD gets further decoded when accessed in SMSDispatcher + if (countInt < 0) { + countInt = 0; } + data = new byte[countInt]; + for (int index=0; index < countInt; index++) { + data[index] = p.readByte(); + } + // BD gets further decoded when accessed in SMSDispatcher + env.bearerData = data; + // link the the filled objects to the SMS env.origAddress = addr; env.origSubaddress = subaddr; @@ -732,6 +734,29 @@ public class SmsMessage extends SmsMessageBase { } /** + * Parses a broadcast SMS, possibly containing a CMAS alert. + */ + SmsCbMessage parseBroadcastSms() { + BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory); + if (bData == null) { + Log.w(LOG_TAG, "BearerData.decode() returned null"); + return null; + } + + if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) { + Log.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData)); + } + + String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC); + SmsCbLocation location = new SmsCbLocation(plmn); + + return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2, + SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location, + mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr, + bData.priority, null, bData.cmasWarningInfo); + } + + /** * {@inheritDoc} */ public MessageClass getMessageClass() { @@ -961,4 +986,12 @@ public class SmsMessage extends SmsMessageBase { return output.toByteArray(); } + + /** + * Returns the list of service category program data, if present. + * @return a list of CdmaSmsCbProgramData objects, or null if not present + */ + List<CdmaSmsCbProgramData> getSmsCbProgramData() { + return mBearerData.serviceCategoryProgramData; + } } 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 6743da01f1d3..e70ff1816980 100755 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -16,28 +16,29 @@ package com.android.internal.telephony.cdma.sms; -import static android.telephony.SmsMessage.ENCODING_16BIT; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; - -import android.util.Log; - +import android.content.res.Resources; +import android.telephony.SmsCbCmasInfo; +import android.telephony.SmsCbMessage; import android.telephony.SmsMessage; - +import android.telephony.cdma.CdmaSmsCbProgramData; import android.text.format.Time; +import android.util.Log; -import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; - import com.android.internal.util.BitwiseInputStream; import com.android.internal.util.BitwiseOutputStream; -import android.content.res.Resources; - +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.TimeZone; +import static android.telephony.SmsMessage.ENCODING_16BIT; +import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; +import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; /** * An object to encode and decode CDMA SMS bearer data. @@ -68,8 +69,8 @@ public final class BearerData { 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_SERVICE_CATEGORY_PROGRAM_DATA = 0x12; - //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13; + 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; //private final static byte SUBPARAM_TP_FAILURE_CAUSE = 0x15; //private final static byte SUBPARAM_ENHANCED_VMN = 0x16; @@ -339,12 +340,68 @@ public final class BearerData { */ public CdmaSmsAddress callbackNumber; + /** + * CMAS warning notification information. + * @see #decodeCmasUserData(BearerData, int) + */ + public SmsCbCmasInfo cmasWarningInfo; + + /** + * The Service Category Program Data subparameter is used to enable and disable + * SMS broadcast service categories to display. If this subparameter is present, + * this field will contain a list of one or more + * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the + * operation(s) to perform. + */ + public List<CdmaSmsCbProgramData> serviceCategoryProgramData; + private static class CodingException extends Exception { public CodingException(String s) { super(s); } } + /** + * Returns the language indicator as a two-character ISO 639 string. + * @return a two character ISO 639 language code + */ + public String getLanguage() { + return getLanguageCodeForValue(language); + } + + /** + * Converts a CDMA language indicator value to an ISO 639 two character language code. + * @param languageValue the CDMA language value to convert + * @return the two character ISO 639 language code for the specified value, or null if unknown + */ + private static String getLanguageCodeForValue(int languageValue) { + switch (languageValue) { + case LANGUAGE_ENGLISH: + return "en"; + + case LANGUAGE_FRENCH: + return "fr"; + + case LANGUAGE_SPANISH: + return "es"; + + case LANGUAGE_JAPANESE: + return "ja"; + + case LANGUAGE_KOREAN: + return "ko"; + + case LANGUAGE_CHINESE: + return "zh"; + + case LANGUAGE_HEBREW: + return "he"; + + default: + return null; + } + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -916,6 +973,9 @@ public final class BearerData { private static String decodeUtf8(byte[] data, int offset, int numFields) throws CodingException { + if (numFields < 0 || (numFields + offset) > data.length) { + throw new CodingException("UTF-8 decode failed: offset or length out of range"); + } try { return new String(data, offset, numFields, "UTF-8"); } catch (java.io.UnsupportedEncodingException ex) { @@ -926,11 +986,12 @@ public final class BearerData { private static String decodeUtf16(byte[] data, int offset, int numFields) throws CodingException { - // Start reading from the next 16-bit aligned boundary after offset. - int padding = offset % 2; - numFields -= (offset + padding) / 2; + int byteCount = numFields * 2; + if (byteCount < 0 || (byteCount + offset) > data.length) { + throw new CodingException("UTF-16 decode failed: offset or length out of range"); + } try { - return new String(data, offset, numFields * 2, "utf-16be"); + return new String(data, offset, byteCount, "utf-16be"); } catch (java.io.UnsupportedEncodingException ex) { throw new CodingException("UTF-16 decode failed: " + ex); } @@ -988,8 +1049,11 @@ public final class BearerData { private static String decodeLatin(byte[] data, int offset, int numFields) throws CodingException { + if (numFields < 0 || (numFields + offset) > data.length) { + throw new CodingException("ISO-8859-1 decode failed: offset or length out of range"); + } try { - return new String(data, offset, numFields - offset, "ISO-8859-1"); + return new String(data, offset, numFields, "ISO-8859-1"); } catch (java.io.UnsupportedEncodingException ex) { throw new CodingException("ISO-8859-1 decode failed: " + ex); } @@ -1032,6 +1096,7 @@ public final class BearerData { userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields); } break; + case UserData.ENCODING_IA5: case UserData.ENCODING_7BIT_ASCII: userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields); @@ -1114,8 +1179,9 @@ public final class BearerData { BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); int dataLen = inStream.available() / 6; // 6-bit packed character encoding. int numFields = bData.userData.numFields; - if ((dataLen > 14) || (dataLen < numFields)) { - throw new CodingException("IS-91 voicemail status decoding failed"); + // dataLen may be > 14 characters due to octet padding + if ((numFields > 14) || (dataLen < numFields)) { + throw new CodingException("IS-91 short message decoding failed"); } StringBuffer strbuf = new StringBuffer(dataLen); for (int i = 0; i < numFields; i++) { @@ -1539,7 +1605,7 @@ public final class BearerData { bData.userResponseCode = inStream.read(8); } if ((! decodeSuccess) || (paramBits > 0)) { - Log.d(LOG_TAG, "USER_REPONSE_CODE decode " + + Log.d(LOG_TAG, "USER_RESPONSE_CODE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); } @@ -1548,27 +1614,240 @@ public final class BearerData { return decodeSuccess; } + private static boolean decodeServiceCategoryProgramData(BearerData bData, + BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException + { + if (inStream.available() < 13) { + throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " + + inStream.available() + " bits available"); + } + + int paramBits = inStream.read(8) * 8; + int msgEncoding = inStream.read(5); + paramBits -= 5; + + if (inStream.available() < paramBits) { + throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only " + + inStream.available() + " bits available (" + paramBits + " bits expected)"); + } + + ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>(); + + final int CATEGORY_FIELD_MIN_SIZE = 6 * 8; + boolean decodeSuccess = false; + while (paramBits >= CATEGORY_FIELD_MIN_SIZE) { + int operation = inStream.read(4); + int category = (inStream.read(8) << 8) | inStream.read(8); + String language = getLanguageCodeForValue(inStream.read(8)); + int maxMessages = inStream.read(8); + int alertOption = inStream.read(4); + int numFields = inStream.read(8); + paramBits -= CATEGORY_FIELD_MIN_SIZE; + + int textBits = getBitsForNumFields(msgEncoding, numFields); + if (paramBits < textBits) { + throw new CodingException("category name is " + textBits + " bits in length," + + " but there are only " + paramBits + " bits available"); + } + + UserData userData = new UserData(); + userData.msgEncoding = msgEncoding; + userData.msgEncodingSet = true; + userData.numFields = numFields; + userData.payload = inStream.readByteArray(textBits); + paramBits -= textBits; + + decodeUserDataPayload(userData, false); + String categoryName = userData.payloadStr; + CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category, + language, maxMessages, alertOption, categoryName); + programDataList.add(programData); + + decodeSuccess = true; + } + + if ((! decodeSuccess) || (paramBits > 0)) { + Log.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " + + (decodeSuccess ? "succeeded" : "failed") + + " (extra bits = " + paramBits + ')'); + } + + inStream.skip(paramBits); + bData.serviceCategoryProgramData = programDataList; + return decodeSuccess; + } + + private static int serviceCategoryToCmasMessageClass(int serviceCategory) { + switch (serviceCategory) { + case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT: + return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; + + case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT: + return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; + + case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT: + return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; + + case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY: + return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; + + case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE: + return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; + + default: + return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; + } + } + + /** + * Calculates the number of bits to read for the specified number of encoded characters. + * @param msgEncoding the message encoding to use + * @param numFields the number of characters to read. For Shift-JIS and Korean encodings, + * this is the number of bytes to read. + * @return the number of bits to read from the stream + * @throws CodingException if the specified encoding is not supported + */ + private static int getBitsForNumFields(int msgEncoding, int numFields) + throws CodingException { + switch (msgEncoding) { + case UserData.ENCODING_OCTET: + case UserData.ENCODING_SHIFT_JIS: + case UserData.ENCODING_KOREAN: + case UserData.ENCODING_LATIN: + case UserData.ENCODING_LATIN_HEBREW: + return numFields * 8; + + case UserData.ENCODING_IA5: + case UserData.ENCODING_7BIT_ASCII: + case UserData.ENCODING_GSM_7BIT_ALPHABET: + return numFields * 7; + + case UserData.ENCODING_UNICODE_16: + return numFields * 16; + + default: + throw new CodingException("unsupported message encoding (" + msgEncoding + ')'); + } + } + + /** + * CMAS message decoding. + * (See TIA-1149-0-1, CMAS over CDMA) + * + * @param serviceCategory is the service category from the SMS envelope + */ + private static void decodeCmasUserData(BearerData bData, int serviceCategory) + throws BitwiseInputStream.AccessException, CodingException { + BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload); + if (inStream.available() < 8) { + throw new CodingException("emergency CB with no CMAE_protocol_version"); + } + int protocolVersion = inStream.read(8); + if (protocolVersion != 0) { + throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion); + } + + int messageClass = serviceCategoryToCmasMessageClass(serviceCategory); + int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN; + int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN; + int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; + int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; + int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; + + while (inStream.available() >= 16) { + int recordType = inStream.read(8); + int recordLen = inStream.read(8); + switch (recordType) { + case 0: // Type 0 elements (Alert text) + UserData alertUserData = new UserData(); + alertUserData.msgEncoding = inStream.read(5); + alertUserData.msgEncodingSet = true; + alertUserData.msgType = 0; + + int numFields; // number of chars to decode + switch (alertUserData.msgEncoding) { + case UserData.ENCODING_OCTET: + case UserData.ENCODING_LATIN: + numFields = recordLen - 1; // subtract 1 byte for encoding + break; + + case UserData.ENCODING_IA5: + case UserData.ENCODING_7BIT_ASCII: + case UserData.ENCODING_GSM_7BIT_ALPHABET: + numFields = ((recordLen * 8) - 5) / 7; // subtract 5 bits for encoding + break; + + case UserData.ENCODING_UNICODE_16: + numFields = (recordLen - 1) / 2; + break; + + default: + numFields = 0; // unsupported encoding + } + + alertUserData.numFields = numFields; + alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5); + decodeUserDataPayload(alertUserData, false); + bData.userData = alertUserData; + break; + + case 1: // Type 1 elements + category = inStream.read(8); + responseType = inStream.read(8); + severity = inStream.read(4); + urgency = inStream.read(4); + certainty = inStream.read(4); + inStream.skip(recordLen * 8 - 28); + break; + + default: + Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType); + inStream.skip(recordLen * 8); + break; + } + } + + bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity, + urgency, certainty); + } + /** * Create BearerData object from serialized representation. * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) * * @param smsData byte array of raw encoded SMS bearer data. - * * @return an instance of BearerData. */ public static BearerData decode(byte[] smsData) { + return decode(smsData, 0); + } + + private static boolean isCmasAlertCategory(int category) { + return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT + && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE; + } + + /** + * Create BearerData object from serialized representation. + * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details) + * + * @param smsData byte array of raw encoded SMS bearer data. + * @param serviceCategory the envelope service category (for CMAS alert handling) + * @return an instance of BearerData. + */ + public static BearerData decode(byte[] smsData, int serviceCategory) { try { BitwiseInputStream inStream = new BitwiseInputStream(smsData); BearerData bData = new BearerData(); int foundSubparamMask = 0; while (inStream.available() > 0) { - boolean decodeSuccess = false; int subparamId = inStream.read(8); int subparamIdBit = 1 << subparamId; if ((foundSubparamMask & subparamIdBit) != 0) { throw new CodingException("illegal duplicate subparameter (" + subparamId + ")"); } + boolean decodeSuccess; switch (subparamId) { case SUBPARAM_MESSAGE_IDENTIFIER: decodeSuccess = decodeMessageId(bData, inStream); @@ -1624,6 +1903,9 @@ public final class BearerData { case SUBPARAM_MESSAGE_DEPOSIT_INDEX: decodeSuccess = decodeDepositIndex(bData, inStream); break; + case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA: + decodeSuccess = decodeServiceCategoryProgramData(bData, inStream); + break; default: throw new CodingException("unsupported bearer data subparameter (" + subparamId + ")"); @@ -1634,7 +1916,10 @@ public final class BearerData { throw new CodingException("missing MESSAGE_IDENTIFIER subparam"); } if (bData.userData != null) { - if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { + if (isCmasAlertCategory(serviceCategory) && bData.priorityIndicatorSet + && bData.priority == SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY) { + decodeCmasUserData(bData, serviceCategory); + } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { if ((foundSubparamMask ^ (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^ (1 << SUBPARAM_USER_DATA)) diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java index 4a60231a451e..f73df56a9ba6 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java @@ -37,6 +37,7 @@ public final class SmsEnvelope { static public final int TELESERVICE_VMN = 0x1003; static public final int TELESERVICE_WAP = 0x1004; static public final int TELESERVICE_WEMT = 0x1005; + static public final int TELESERVICE_SCPT = 0x1006; /** * The following are defined as extensions to the standard teleservices @@ -46,14 +47,17 @@ public final class SmsEnvelope { // number of messages waiting, it's used by some CDMA carriers for a voice mail count. static public final int TELESERVICE_MWI = 0x40000; - // ServiceCategories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1 - //static public final int SERVICECATEGORY_EMERGENCY = 0x0010; + // Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1 + // static final int SERVICE_CATEGORY_EMERGENCY = 0x0001; //... - /** - * maximum lengths for fields as defined in ril_cdma_sms.h - */ - static public final int SMS_BEARER_DATA_MAX = 255; + // CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1 + public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT = 0x1000; + public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT = 0x1001; + public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT = 0x1002; + public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 0x1003; + public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE = 0x1004; + public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE = 0x10ff; /** * Provides the type of a SMS message like point to point, broadcast or acknowledge diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java index 189d97db0f75..599c2b393a6b 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java @@ -32,9 +32,9 @@ public class UserData { public static final int ENCODING_7BIT_ASCII = 0x02; public static final int ENCODING_IA5 = 0x03; public static final int ENCODING_UNICODE_16 = 0x04; - //public static final int ENCODING_SHIFT_JIS = 0x05; - //public static final int ENCODING_KOREAN = 0x06; - //public static final int ENCODING_LATIN_HEBREW = 0x07; + public static final int ENCODING_SHIFT_JIS = 0x05; + public static final int ENCODING_KOREAN = 0x06; + public static final int ENCODING_LATIN_HEBREW = 0x07; public static final int ENCODING_LATIN = 0x08; public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09; public static final int ENCODING_GSM_DCS = 0x0A; diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index 8a75f51ff5ff..931c662d27e9 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -26,6 +26,7 @@ import android.os.SystemProperties; import android.provider.Telephony.Sms; import android.provider.Telephony.Sms.Intents; import android.telephony.PhoneNumberUtils; +import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.telephony.SmsManager; import android.telephony.gsm.GsmCellLocation; @@ -318,24 +319,18 @@ public final class GsmSMSDispatcher extends SMSDispatcher { * concatenated message */ private static final class SmsCbConcatInfo { - private final SmsCbHeader mHeader; - - private final String mPlmn; - private final int mLac; - - private final int mCid; + private final SmsCbHeader mHeader; + private final SmsCbLocation mLocation; - public SmsCbConcatInfo(SmsCbHeader header, String plmn, int lac, int cid) { + public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) { mHeader = header; - mPlmn = plmn; - mLac = lac; - mCid = cid; + mLocation = location; } @Override public int hashCode() { - return mHeader.messageIdentifier * 31 + mHeader.updateNumber; + return (mHeader.getSerialNumber() * 31) + mLocation.hashCode(); } @Override @@ -343,49 +338,28 @@ public final class GsmSMSDispatcher extends SMSDispatcher { if (obj instanceof SmsCbConcatInfo) { SmsCbConcatInfo other = (SmsCbConcatInfo)obj; - // Two pages match if all header attributes (except the page - // index) are identical, and both pages belong to the same - // location (which is also determined by the scope parameter) - if (mHeader.geographicalScope == other.mHeader.geographicalScope - && mHeader.messageCode == other.mHeader.messageCode - && mHeader.updateNumber == other.mHeader.updateNumber - && mHeader.messageIdentifier == other.mHeader.messageIdentifier - && mHeader.dataCodingScheme == other.mHeader.dataCodingScheme - && mHeader.nrOfPages == other.mHeader.nrOfPages) { - return matchesLocation(other.mPlmn, other.mLac, other.mCid); - } + // Two pages match if they have the same serial number (which includes the + // geographical scope and update number), and both pages belong to the same + // location (PLMN, plus LAC and CID if these are part of the geographical scope). + return mHeader.getSerialNumber() == other.mHeader.getSerialNumber() + && mLocation.equals(other.mLocation); } return false; } /** - * Checks if this concatenation info matches the given location. The - * granularity of the match depends on the geographical scope. + * Compare the location code for this message to the current location code. The match is + * relative to the geographical scope of the message, which determines whether the LAC + * and Cell ID are saved in mLocation or set to -1 to match all values. * - * @param plmn PLMN - * @param lac Location area code - * @param cid Cell ID - * @return true if matching, false otherwise + * @param plmn the current PLMN + * @param lac the current Location Area (GSM) or Service Area (UMTS) + * @param cid the current Cell ID + * @return true if this message is valid for the current location; false otherwise */ public boolean matchesLocation(String plmn, int lac, int cid) { - switch (mHeader.geographicalScope) { - case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: - case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: - if (mCid != cid) { - return false; - } - // deliberate fall-through - case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: - if (mLac != lac) { - return false; - } - // deliberate fall-through - case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: - return mPlmn != null && mPlmn.equals(plmn); - } - - return false; + return mLocation.isInLocationArea(plmn, lac, cid); } } @@ -421,24 +395,42 @@ public final class GsmSMSDispatcher extends SMSDispatcher { int lac = cellLocation.getLac(); int cid = cellLocation.getCid(); + SmsCbLocation location; + switch (header.getGeographicalScope()) { + case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE: + location = new SmsCbLocation(plmn, lac, -1); + break; + + case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE: + case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: + location = new SmsCbLocation(plmn, lac, cid); + break; + + case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE: + default: + location = new SmsCbLocation(plmn); + break; + } + byte[][] pdus; - if (header.nrOfPages > 1) { + int pageCount = header.getNumberOfPages(); + if (pageCount > 1) { // Multi-page message - SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid); + SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location); // Try to find other pages of the same message pdus = mSmsCbPageMap.get(concatInfo); if (pdus == null) { - // This it the first page of this message, make room for all + // This is the first page of this message, make room for all // pages and keep until complete - pdus = new byte[header.nrOfPages][]; + pdus = new byte[pageCount][]; mSmsCbPageMap.put(concatInfo, pdus); } // Page parameter is one-based - pdus[header.pageIndex - 1] = receivedPdu; + pdus[header.getPageIndex() - 1] = receivedPdu; for (int i = 0; i < pdus.length; i++) { if (pdus[i] == null) { @@ -455,8 +447,8 @@ public final class GsmSMSDispatcher extends SMSDispatcher { pdus[0] = receivedPdu; } - boolean isEmergencyMessage = SmsCbHeader.isEmergencyMessage(header.messageIdentifier); - dispatchBroadcastPdus(pdus, isEmergencyMessage); + SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus); + dispatchBroadcastMessage(message); // Remove messages that are out of scope to prevent the map from // growing indefinitely, containing incomplete messages that were diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java new file mode 100644 index 000000000000..dc9554a56d80 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2012 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.internal.telephony.gsm; + +import android.telephony.SmsCbLocation; +import android.telephony.SmsCbMessage; +import android.util.Pair; + +import com.android.internal.telephony.GsmAlphabet; + +import java.io.UnsupportedEncodingException; + +/** + * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is + * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases. + */ +public class GsmSmsCbMessage { + + /** + * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5. + */ + private static final String[] LANGUAGE_CODES_GROUP_0 = { + "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu", + "pl", null + }; + + /** + * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5. + */ + private static final String[] LANGUAGE_CODES_GROUP_2 = { + "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null, + null, null + }; + + private static final char CARRIAGE_RETURN = 0x0d; + + private static final int PDU_BODY_PAGE_LENGTH = 82; + + /** Utility class with only static methods. */ + private GsmSmsCbMessage() { } + + /** + * Create a new SmsCbMessage object from a header object plus one or more received PDUs. + * + * @param pdus PDU bytes + */ + static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location, + byte[][] pdus) throws IllegalArgumentException { + if (header.isEtwsPrimaryNotification()) { + return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, + header.getGeographicalScope(), header.getSerialNumber(), + location, header.getServiceCategory(), + null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, + header.getEtwsInfo(), header.getCmasInfo()); + } else { + String language = null; + StringBuilder sb = new StringBuilder(); + for (byte[] pdu : pdus) { + Pair<String, String> p = parseBody(header, pdu); + language = p.first; + sb.append(p.second); + } + int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY + : SmsCbMessage.MESSAGE_PRIORITY_NORMAL; + + return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP, + header.getGeographicalScope(), header.getSerialNumber(), location, + header.getServiceCategory(), language, sb.toString(), priority, + header.getEtwsInfo(), header.getCmasInfo()); + } + } + + /** + * Create a new SmsCbMessage object from one or more received PDUs. This is used by some + * CellBroadcastReceiver test cases, because SmsCbHeader is now package local. + * + * @param location the location (geographical scope) for the message + * @param pdus PDU bytes + */ + public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus) + throws IllegalArgumentException { + SmsCbHeader header = new SmsCbHeader(pdus[0]); + return createSmsCbMessage(header, location, pdus); + } + + /** + * Parse and unpack the body text according to the encoding in the DCS. + * After completing successfully this method will have assigned the body + * text into mBody, and optionally the language code into mLanguage + * + * @param header the message header to use + * @param pdu the PDU to decode + * @return a Pair of Strings containing the language and body of the message + */ + private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) { + int encoding; + String language = null; + boolean hasLanguageIndicator = false; + int dataCodingScheme = header.getDataCodingScheme(); + + // Extract encoding and language from DCS, as defined in 3gpp TS 23.038, + // section 5. + switch ((dataCodingScheme & 0xf0) >> 4) { + case 0x00: + encoding = android.telephony.SmsMessage.ENCODING_7BIT; + language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f]; + break; + + case 0x01: + hasLanguageIndicator = true; + if ((dataCodingScheme & 0x0f) == 0x01) { + encoding = android.telephony.SmsMessage.ENCODING_16BIT; + } else { + encoding = android.telephony.SmsMessage.ENCODING_7BIT; + } + break; + + case 0x02: + encoding = android.telephony.SmsMessage.ENCODING_7BIT; + language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f]; + break; + + case 0x03: + encoding = android.telephony.SmsMessage.ENCODING_7BIT; + break; + + case 0x04: + case 0x05: + switch ((dataCodingScheme & 0x0c) >> 2) { + case 0x01: + encoding = android.telephony.SmsMessage.ENCODING_8BIT; + break; + + case 0x02: + encoding = android.telephony.SmsMessage.ENCODING_16BIT; + break; + + case 0x00: + default: + encoding = android.telephony.SmsMessage.ENCODING_7BIT; + break; + } + break; + + case 0x06: + case 0x07: + // Compression not supported + case 0x09: + // UDH structure not supported + case 0x0e: + // Defined by the WAP forum not supported + throw new IllegalArgumentException("Unsupported GSM dataCodingScheme " + + dataCodingScheme); + + case 0x0f: + if (((dataCodingScheme & 0x04) >> 2) == 0x01) { + encoding = android.telephony.SmsMessage.ENCODING_8BIT; + } else { + encoding = android.telephony.SmsMessage.ENCODING_7BIT; + } + break; + + default: + // Reserved values are to be treated as 7-bit + encoding = android.telephony.SmsMessage.ENCODING_7BIT; + break; + } + + if (header.isUmtsFormat()) { + // Payload may contain multiple pages + int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH]; + + if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) + * nrPages) { + throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match " + + nrPages + " pages"); + } + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < nrPages; i++) { + // Each page is 82 bytes followed by a length octet indicating + // the number of useful octets within those 82 + int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i; + int length = pdu[offset + PDU_BODY_PAGE_LENGTH]; + + if (length > PDU_BODY_PAGE_LENGTH) { + throw new IllegalArgumentException("Page length " + length + + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH); + } + + Pair<String, String> p = unpackBody(pdu, encoding, offset, length, + hasLanguageIndicator, language); + language = p.first; + sb.append(p.second); + } + return new Pair<String, String>(language, sb.toString()); + } else { + // Payload is one single page + int offset = SmsCbHeader.PDU_HEADER_LENGTH; + int length = pdu.length - offset; + + return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language); + } + } + + /** + * Unpack body text from the pdu using the given encoding, position and + * length within the pdu + * + * @param pdu The pdu + * @param encoding The encoding, as derived from the DCS + * @param offset Position of the first byte to unpack + * @param length Number of bytes to unpack + * @param hasLanguageIndicator true if the body text is preceded by a + * language indicator. If so, this method will as a side-effect + * assign the extracted language code into mLanguage + * @param language the language to return if hasLanguageIndicator is false + * @return a Pair of Strings containing the language and body of the message + */ + private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length, + boolean hasLanguageIndicator, String language) { + String body = null; + + switch (encoding) { + case android.telephony.SmsMessage.ENCODING_7BIT: + body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7); + + if (hasLanguageIndicator && body != null && body.length() > 2) { + // Language is two GSM characters followed by a CR. + // The actual body text is offset by 3 characters. + language = body.substring(0, 2); + body = body.substring(3); + } + break; + + case android.telephony.SmsMessage.ENCODING_16BIT: + if (hasLanguageIndicator && pdu.length >= offset + 2) { + // Language is two GSM characters. + // The actual body text is offset by 2 bytes. + language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2); + offset += 2; + length -= 2; + } + + try { + body = new String(pdu, offset, (length & 0xfffe), "utf-16"); + } catch (UnsupportedEncodingException e) { + // Apparently it wasn't valid UTF-16. + throw new IllegalArgumentException("Error decoding UTF-16 message", e); + } + break; + + default: + break; + } + + if (body != null) { + // Remove trailing carriage return + for (int i = body.length() - 1; i >= 0; i--) { + if (body.charAt(i) != CARRIAGE_RETURN) { + body = body.substring(0, i + 1); + break; + } + } + } else { + body = ""; + } + + return new Pair<String, String>(language, body); + } +} diff --git a/telephony/java/android/telephony/SmsCbConstants.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java index a1b4adf19baf..ebb4666f91f3 100644 --- a/telephony/java/android/telephony/SmsCbConstants.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2012 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. @@ -14,14 +14,22 @@ * limitations under the License. */ -package android.telephony; +package com.android.internal.telephony.gsm; /** - * Constants used in SMS Cell Broadcast messages. + * Constants used in SMS Cell Broadcast messages (see 3GPP TS 23.041). This class is used by the + * boot-time broadcast channel enable and database upgrade code in CellBroadcastReceiver, so it + * is public, but should be avoided in favor of the radio technology independent constants in + * {@link android.telephony.SmsCbMessage}, {@link android.telephony.SmsCbEtwsInfo}, and + * {@link android.telephony.SmsCbCmasInfo} classes. * * {@hide} */ -public interface SmsCbConstants { +public class SmsCbConstants { + + /** Private constructor for utility class. */ + private SmsCbConstants() { } + /** Start of PWS Message Identifier range (includes ETWS and CMAS). */ public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100; @@ -94,11 +102,11 @@ public interface SmsCbConstants { /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */ public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER = 0x18FF; - /** ETWS message code flag to activate the popup display. */ - public static final int MESSAGE_CODE_ETWS_ACTIVATE_POPUP = 0x100; + /** ETWS serial number flag to activate the popup display. */ + public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP = 0x1000; - /** ETWS message code flag to activate the emergency user alert. */ - public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT = 0x200; + /** ETWS serial number flag to activate the emergency user alert. */ + public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT = 0x2000; /** ETWS warning type value for earthquake. */ public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00; diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java index 8e6b79b322da..569204489c45 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java @@ -16,28 +16,42 @@ package com.android.internal.telephony.gsm; -import android.telephony.SmsCbConstants; +import android.telephony.SmsCbCmasInfo; +import android.telephony.SmsCbEtwsInfo; + +import java.util.Arrays; + +/** + * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by + * CellBroadcastReceiver test cases, but should not be used by applications. + * + * All relevant header information is now sent as a Parcelable + * {@link android.telephony.SmsCbMessage} object in the "message" extra of the + * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or + * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent. + * The raw PDU is no longer sent to SMS CB applications. + */ +class SmsCbHeader { -public class SmsCbHeader implements SmsCbConstants { /** * Length of SMS-CB header */ - public static final int PDU_HEADER_LENGTH = 6; + static final int PDU_HEADER_LENGTH = 6; /** * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1 */ - public static final int FORMAT_GSM = 1; + static final int FORMAT_GSM = 1; /** * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2 */ - public static final int FORMAT_UMTS = 2; + static final int FORMAT_UMTS = 2; /** * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3 */ - public static final int FORMAT_ETWS_PRIMARY = 3; + static final int FORMAT_ETWS_PRIMARY = 3; /** * Message type value as defined in 3gpp TS 25.324, section 11.1. @@ -47,34 +61,34 @@ public class SmsCbHeader implements SmsCbConstants { /** * Length of GSM pdus */ - public static final int PDU_LENGTH_GSM = 88; + private static final int PDU_LENGTH_GSM = 88; /** * Maximum length of ETWS primary message GSM pdus */ - public static final int PDU_LENGTH_ETWS = 56; - - public final int geographicalScope; + private static final int PDU_LENGTH_ETWS = 56; - public final int messageCode; + private final int geographicalScope; - public final int updateNumber; + /** The serial number combines geographical scope, message code, and update number. */ + private final int serialNumber; - public final int messageIdentifier; + /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */ + private final int messageIdentifier; - public final int dataCodingScheme; + private final int dataCodingScheme; - public final int pageIndex; + private final int pageIndex; - public final int nrOfPages; + private final int nrOfPages; - public final int format; + private final int format; - public final boolean etwsEmergencyUserAlert; + /** ETWS warning notification info. */ + private final SmsCbEtwsInfo mEtwsInfo; - public final boolean etwsPopup; - - public final int etwsWarningType; + /** CMAS warning notification info. */ + private final SmsCbCmasInfo mCmasInfo; public SmsCbHeader(byte[] pdu) throws IllegalArgumentException { if (pdu == null || pdu.length < PDU_HEADER_LENGTH) { @@ -83,22 +97,31 @@ public class SmsCbHeader implements SmsCbConstants { if (pdu.length <= PDU_LENGTH_ETWS) { format = FORMAT_ETWS_PRIMARY; - geographicalScope = -1; //not applicable - messageCode = -1; - updateNumber = -1; + geographicalScope = (pdu[0] & 0xc0) >> 6; + serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff); messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff); dataCodingScheme = -1; pageIndex = -1; nrOfPages = -1; - etwsEmergencyUserAlert = (pdu[4] & 0x1) != 0; - etwsPopup = (pdu[5] & 0x80) != 0; - etwsWarningType = (pdu[4] & 0xfe) >> 1; + boolean emergencyUserAlert = (pdu[4] & 0x1) != 0; + boolean activatePopup = (pdu[5] & 0x80) != 0; + int warningType = (pdu[4] & 0xfe) >> 1; + byte[] warningSecurityInfo; + // copy the Warning-Security-Information, if present + if (pdu.length > PDU_HEADER_LENGTH) { + warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length); + } else { + warningSecurityInfo = null; + } + mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, + warningSecurityInfo); + mCmasInfo = null; + return; // skip the ETWS/CMAS initialization code for regular notifications } else if (pdu.length <= PDU_LENGTH_GSM) { // GSM pdus are no more than 88 bytes format = FORMAT_GSM; geographicalScope = (pdu[0] & 0xc0) >> 6; - messageCode = ((pdu[0] & 0x3f) << 4) | ((pdu[1] & 0xf0) >> 4); - updateNumber = pdu[1] & 0x0f; + serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff); messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff); dataCodingScheme = pdu[4] & 0xff; @@ -113,9 +136,6 @@ public class SmsCbHeader implements SmsCbConstants { this.pageIndex = pageIndex; this.nrOfPages = nrOfPages; - etwsEmergencyUserAlert = false; - etwsPopup = false; - etwsWarningType = -1; } else { // UMTS pdus are always at least 90 bytes since the payload includes // a number-of-pages octet and also one length octet per page @@ -129,8 +149,7 @@ public class SmsCbHeader implements SmsCbConstants { messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff; geographicalScope = (pdu[3] & 0xc0) >> 6; - messageCode = ((pdu[3] & 0x3f) << 4) | ((pdu[4] & 0xf0) >> 4); - updateNumber = pdu[4] & 0x0f; + serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff); dataCodingScheme = pdu[5] & 0xff; // We will always consider a UMTS message as having one single page @@ -138,75 +157,251 @@ public class SmsCbHeader implements SmsCbConstants { // actual payload may contain several pages. pageIndex = 1; nrOfPages = 1; - etwsEmergencyUserAlert = false; - etwsPopup = false; - etwsWarningType = -1; } + + if (isEtwsMessage()) { + boolean emergencyUserAlert = isEtwsEmergencyUserAlert(); + boolean activatePopup = isEtwsPopupAlert(); + int warningType = getEtwsWarningType(); + mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, null); + mCmasInfo = null; + } else if (isCmasMessage()) { + int messageClass = getCmasMessageClass(); + int severity = getCmasSeverity(); + int urgency = getCmasUrgency(); + int certainty = getCmasCertainty(); + mEtwsInfo = null; + mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, + SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty); + } else { + mEtwsInfo = null; + mCmasInfo = null; + } + } + + int getGeographicalScope() { + return geographicalScope; + } + + int getSerialNumber() { + return serialNumber; + } + + int getServiceCategory() { + return messageIdentifier; + } + + int getDataCodingScheme() { + return dataCodingScheme; + } + + int getPageIndex() { + return pageIndex; + } + + int getNumberOfPages() { + return nrOfPages; + } + + SmsCbEtwsInfo getEtwsInfo() { + return mEtwsInfo; + } + + SmsCbCmasInfo getCmasInfo() { + return mCmasInfo; + } + + /** + * Return whether this broadcast is an emergency (PWS) message type. + * @return true if this message is emergency type; false otherwise + */ + boolean isEmergencyMessage() { + return messageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER + && messageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER; + } + + /** + * Return whether this broadcast is an ETWS emergency message type. + * @return true if this message is ETWS emergency type; false otherwise + */ + private boolean isEtwsMessage() { + return (messageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK) + == SmsCbConstants.MESSAGE_ID_ETWS_TYPE; } /** - * Return whether the specified message ID is an emergency (PWS) message type. - * This method is static and takes an argument so that it can be used by - * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU. - * @param id the message identifier to check - * @return true if the message is emergency type; false otherwise + * Return whether this broadcast is an ETWS primary notification. + * @return true if this message is an ETWS primary notification; false otherwise */ - public static boolean isEmergencyMessage(int id) { - return id >= MESSAGE_ID_PWS_FIRST_IDENTIFIER && id <= MESSAGE_ID_PWS_LAST_IDENTIFIER; + boolean isEtwsPrimaryNotification() { + return format == FORMAT_ETWS_PRIMARY; } /** - * Return whether the specified message ID is an ETWS emergency message type. - * This method is static and takes an argument so that it can be used by - * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU. - * @param id the message identifier to check - * @return true if the message is ETWS emergency type; false otherwise + * Return whether this broadcast is in UMTS format. + * @return true if this message is in UMTS format; false otherwise */ - public static boolean isEtwsMessage(int id) { - return (id & MESSAGE_ID_ETWS_TYPE_MASK) == MESSAGE_ID_ETWS_TYPE; + boolean isUmtsFormat() { + return format == FORMAT_UMTS; } /** - * Return whether the specified message ID is a CMAS emergency message type. - * This method is static and takes an argument so that it can be used by - * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU. - * @param id the message identifier to check - * @return true if the message is CMAS emergency type; false otherwise + * Return whether this message is a CMAS emergency message type. + * @return true if this message is CMAS emergency type; false otherwise */ - public static boolean isCmasMessage(int id) { - return id >= MESSAGE_ID_CMAS_FIRST_IDENTIFIER && id <= MESSAGE_ID_CMAS_LAST_IDENTIFIER; + private boolean isCmasMessage() { + return messageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER + && messageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER; } /** - * Return whether the specified message code indicates an ETWS popup alert. - * This method is static and takes an argument so that it can be used by - * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU. + * Return whether the popup alert flag is set for an ETWS warning notification. * This method assumes that the message ID has already been checked for ETWS type. * - * @param messageCode the message code to check * @return true if the message code indicates a popup alert should be displayed */ - public static boolean isEtwsPopupAlert(int messageCode) { - return (messageCode & MESSAGE_CODE_ETWS_ACTIVATE_POPUP) != 0; + private boolean isEtwsPopupAlert() { + return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0; } /** - * Return whether the specified message code indicates an ETWS emergency user alert. - * This method is static and takes an argument so that it can be used by - * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU. + * Return whether the emergency user alert flag is set for an ETWS warning notification. * This method assumes that the message ID has already been checked for ETWS type. * - * @param messageCode the message code to check * @return true if the message code indicates an emergency user alert */ - public static boolean isEtwsEmergencyUserAlert(int messageCode) { - return (messageCode & MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT) != 0; + private boolean isEtwsEmergencyUserAlert() { + return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0; + } + + /** + * Returns the warning type for an ETWS warning notification. + * This method assumes that the message ID has already been checked for ETWS type. + * + * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24 + */ + private int getEtwsWarningType() { + return messageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING; + } + + /** + * Returns the message class for a CMAS warning notification. + * This method assumes that the message ID has already been checked for CMAS type. + * @return the CMAS message class as defined in {@link SmsCbCmasInfo} + */ + private int getCmasMessageClass() { + switch (messageIdentifier) { + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL: + return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: + return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: + return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY: + return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST: + return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE: + return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE: + return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE; + + default: + return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN; + } + } + + /** + * Returns the severity for a CMAS warning notification. This is only available for extreme + * and severe alerts, not for other types such as Presidential Level and AMBER alerts. + * This method assumes that the message ID has already been checked for CMAS type. + * @return the CMAS severity as defined in {@link SmsCbCmasInfo} + */ + private int getCmasSeverity() { + switch (messageIdentifier) { + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: + return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: + return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE; + + default: + return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN; + } + } + + /** + * Returns the urgency for a CMAS warning notification. This is only available for extreme + * and severe alerts, not for other types such as Presidential Level and AMBER alerts. + * This method assumes that the message ID has already been checked for CMAS type. + * @return the CMAS urgency as defined in {@link SmsCbCmasInfo} + */ + private int getCmasUrgency() { + switch (messageIdentifier) { + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: + return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: + return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED; + + default: + return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN; + } + } + + /** + * Returns the certainty for a CMAS warning notification. This is only available for extreme + * and severe alerts, not for other types such as Presidential Level and AMBER alerts. + * This method assumes that the message ID has already been checked for CMAS type. + * @return the CMAS certainty as defined in {@link SmsCbCmasInfo} + */ + private int getCmasCertainty() { + switch (messageIdentifier) { + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED: + return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED; + + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY: + case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY: + return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY; + + default: + return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN; + } } @Override public String toString() { - return "SmsCbHeader{GS=" + geographicalScope + ", messageCode=0x" + - Integer.toHexString(messageCode) + ", updateNumber=" + updateNumber + + return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" + + Integer.toHexString(serialNumber) + ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) + ", DCS=0x" + Integer.toHexString(dataCodingScheme) + ", page " + pageIndex + " of " + nrOfPages + '}'; diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java new file mode 100644 index 000000000000..3c9c0b2cbaa3 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java @@ -0,0 +1,708 @@ +/* + * Copyright (C) 2012 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.internal.telephony.cdma; + +import android.os.Parcel; +import android.telephony.SmsCbCmasInfo; +import android.telephony.SmsCbMessage; +import android.telephony.cdma.CdmaSmsCbProgramData; +import android.test.AndroidTestCase; +import android.util.Log; + +import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.IccUtils; +import com.android.internal.telephony.cdma.sms.BearerData; +import com.android.internal.telephony.cdma.sms.CdmaSmsAddress; +import com.android.internal.telephony.cdma.sms.SmsEnvelope; +import com.android.internal.telephony.cdma.sms.UserData; +import com.android.internal.util.BitwiseOutputStream; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +/** + * Test cases for basic SmsCbMessage operation for CDMA. + */ +public class CdmaSmsCbTest extends AndroidTestCase { + + /* Copy of private subparameter identifier constants from BearerData class. */ + private static final byte SUBPARAM_MESSAGE_IDENTIFIER = (byte) 0x00; + private static final byte SUBPARAM_USER_DATA = (byte) 0x01; + private static final byte SUBPARAM_PRIORITY_INDICATOR = (byte) 0x08; + private static final byte SUBPARAM_LANGUAGE_INDICATOR = (byte) 0x0D; + private static final byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA = 0x12; + + /** + * Initialize a Parcel for an incoming CDMA cell broadcast. The caller will write the + * bearer data and then convert it to an SmsMessage. + * @param serviceCategory the CDMA service category + * @return the initialized Parcel + */ + private static Parcel createBroadcastParcel(int serviceCategory) { + Parcel p = Parcel.obtain(); + + p.writeInt(SmsEnvelope.TELESERVICE_NOT_SET); + p.writeByte((byte) 1); // non-zero for MESSAGE_TYPE_BROADCAST + p.writeInt(serviceCategory); + + // dummy address (RIL may generate a different dummy address for broadcasts) + p.writeInt(CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF); // sAddress.digit_mode + p.writeInt(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK); // sAddress.number_mode + p.writeInt(CdmaSmsAddress.TON_UNKNOWN); // sAddress.number_type + p.writeInt(CdmaSmsAddress.NUMBERING_PLAN_ISDN_TELEPHONY); // sAddress.number_plan + p.writeByte((byte) 0); // sAddress.number_of_digits + p.writeInt((byte) 0); // sSubAddress.subaddressType + p.writeByte((byte) 0); // sSubAddress.odd + p.writeByte((byte) 0); // sSubAddress.number_of_digits + return p; + } + + /** + * Initialize a BitwiseOutputStream with the CDMA bearer data subparameters except for + * user data. The caller will append the user data and add it to the parcel. + * @param messageId the 16-bit message identifier + * @param priority message priority + * @param language message language code + * @return the initialized BitwiseOutputStream + */ + private static BitwiseOutputStream createBearerDataStream(int messageId, int priority, + int language) throws BitwiseOutputStream.AccessException { + BitwiseOutputStream bos = new BitwiseOutputStream(10); + bos.write(8, SUBPARAM_MESSAGE_IDENTIFIER); + bos.write(8, 3); // length: 3 bytes + bos.write(4, BearerData.MESSAGE_TYPE_DELIVER); + bos.write(8, ((messageId >>> 8) & 0xff)); + bos.write(8, (messageId & 0xff)); + bos.write(1, 0); // no User Data Header + bos.write(3, 0); // reserved + + if (priority != -1) { + bos.write(8, SUBPARAM_PRIORITY_INDICATOR); + bos.write(8, 1); // length: 1 byte + bos.write(2, (priority & 0x03)); + bos.write(6, 0); // reserved + } + + if (language != -1) { + bos.write(8, SUBPARAM_LANGUAGE_INDICATOR); + bos.write(8, 1); // length: 1 byte + bos.write(8, (language & 0xff)); + } + + return bos; + } + + /** + * Write the bearer data array to the parcel, then return a new SmsMessage from the parcel. + * @param p the parcel containing the CDMA SMS headers + * @param bearerData the bearer data byte array to append to the parcel + * @return the new SmsMessage created from the parcel + */ + private static SmsMessage createMessageFromParcel(Parcel p, byte[] bearerData) { + p.writeInt(bearerData.length); + for (byte b : bearerData) { + p.writeByte(b); + } + p.setDataPosition(0); // reset position for reading + SmsMessage message = SmsMessage.newFromParcel(p); + p.recycle(); + return message; + } + + /** + * Create a parcel for an incoming CMAS broadcast, then return a new SmsMessage created + * from the parcel. + * @param serviceCategory the CDMA service category + * @param messageId the 16-bit message identifier + * @param priority message priority + * @param language message language code + * @param body message body + * @param cmasCategory CMAS category (or -1 to skip adding CMAS type 1 elements record) + * @param responseType CMAS response type + * @param severity CMAS severity + * @param urgency CMAS urgency + * @param certainty CMAS certainty + * @return the newly created SmsMessage object + */ + private static SmsMessage createCmasSmsMessage(int serviceCategory, int messageId, int priority, + int language, int encoding, String body, int cmasCategory, int responseType, + int severity, int urgency, int certainty) throws Exception { + BitwiseOutputStream cmasBos = new BitwiseOutputStream(10); + cmasBos.write(8, 0); // CMAE protocol version 0 + + if (body != null) { + cmasBos.write(8, 0); // Type 0 elements (alert text) + encodeBody(encoding, body, true, cmasBos); + } + + if (cmasCategory != SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN) { + cmasBos.write(8, 1); // Type 1 elements + cmasBos.write(8, 4); // length: 4 bytes + cmasBos.write(8, (cmasCategory & 0xff)); + cmasBos.write(8, (responseType & 0xff)); + cmasBos.write(4, (severity & 0x0f)); + cmasBos.write(4, (urgency & 0x0f)); + cmasBos.write(4, (certainty & 0x0f)); + cmasBos.write(4, 0); // pad to octet boundary + } + + byte[] cmasUserData = cmasBos.toByteArray(); + + Parcel p = createBroadcastParcel(serviceCategory); + BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language); + + bos.write(8, SUBPARAM_USER_DATA); + bos.write(8, cmasUserData.length + 2); // add 2 bytes for msg_encoding and num_fields + bos.write(5, UserData.ENCODING_OCTET); + bos.write(8, cmasUserData.length); + bos.writeByteArray(cmasUserData.length * 8, cmasUserData); + bos.write(3, 0); // pad to byte boundary + + return createMessageFromParcel(p, bos.toByteArray()); + } + + /** + * Create a parcel for an incoming CDMA cell broadcast, then return a new SmsMessage created + * from the parcel. + * @param serviceCategory the CDMA service category + * @param messageId the 16-bit message identifier + * @param priority message priority + * @param language message language code + * @param encoding user data encoding method + * @param body the message body + * @return the newly created SmsMessage object + */ + private static SmsMessage createBroadcastSmsMessage(int serviceCategory, int messageId, + int priority, int language, int encoding, String body) throws Exception { + Parcel p = createBroadcastParcel(serviceCategory); + BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language); + + bos.write(8, SUBPARAM_USER_DATA); + encodeBody(encoding, body, false, bos); + + return createMessageFromParcel(p, bos.toByteArray()); + } + + /** + * Append the message length, encoding, and body to the BearerData output stream. + * This is used for writing the User Data subparameter for non-CMAS broadcasts and for + * writing the alert text for CMAS broadcasts. + * @param encoding one of the CDMA UserData encoding values + * @param body the message body + * @param isCmasRecord true if this is a CMAS type 0 elements record; false for user data + * @param bos the BitwiseOutputStream to write to + * @throws Exception on any encoding error + */ + private static void encodeBody(int encoding, String body, boolean isCmasRecord, + BitwiseOutputStream bos) throws Exception { + if (encoding == UserData.ENCODING_7BIT_ASCII || encoding == UserData.ENCODING_IA5) { + int charCount = body.length(); + int recordBits = (charCount * 7) + 5; // add 5 bits for char set field + int recordOctets = (recordBits + 7) / 8; // round up to octet boundary + int padBits = (recordOctets * 8) - recordBits; + + if (!isCmasRecord) { + recordOctets++; // add 8 bits for num_fields + } + + bos.write(8, recordOctets); + bos.write(5, (encoding & 0x1f)); + + if (!isCmasRecord) { + bos.write(8, charCount); + } + + for (int i = 0; i < charCount; i++) { + bos.write(7, body.charAt(i)); + } + + bos.write(padBits, 0); // pad to octet boundary + } else if (encoding == UserData.ENCODING_GSM_7BIT_ALPHABET + || encoding == UserData.ENCODING_GSM_DCS) { + // convert to 7-bit packed encoding with septet count in index 0 of byte array + byte[] encodedBody = GsmAlphabet.stringToGsm7BitPacked(body); + + int charCount = encodedBody[0]; // septet count + int recordBits = (charCount * 7) + 5; // add 5 bits for char set field + int recordOctets = (recordBits + 7) / 8; // round up to octet boundary + int padBits = (recordOctets * 8) - recordBits; + + if (!isCmasRecord) { + recordOctets++; // add 8 bits for num_fields + if (encoding == UserData.ENCODING_GSM_DCS) { + recordOctets++; // add 8 bits for DCS (message type) + } + } + + bos.write(8, recordOctets); + bos.write(5, (encoding & 0x1f)); + + if (!isCmasRecord && encoding == UserData.ENCODING_GSM_DCS) { + bos.write(8, 0); // GSM DCS: 7 bit default alphabet, no msg class + } + + if (!isCmasRecord) { + bos.write(8, charCount); + } + byte[] bodySeptets = Arrays.copyOfRange(encodedBody, 1, encodedBody.length); + bos.writeByteArray(charCount * 7, bodySeptets); + bos.write(padBits, 0); // pad to octet boundary + } else if (encoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) { + // 6 bit packed encoding with 0x20 offset (ASCII 0x20 - 0x60) + int charCount = body.length(); + int recordBits = (charCount * 6) + 21; // add 21 bits for header fields + int recordOctets = (recordBits + 7) / 8; // round up to octet boundary + int padBits = (recordOctets * 8) - recordBits; + + bos.write(8, recordOctets); + + bos.write(5, (encoding & 0x1f)); + bos.write(8, UserData.IS91_MSG_TYPE_SHORT_MESSAGE); + bos.write(8, charCount); + + for (int i = 0; i < charCount; i++) { + bos.write(6, ((int) body.charAt(i) - 0x20)); + } + + bos.write(padBits, 0); // pad to octet boundary + } else { + byte[] encodedBody; + switch (encoding) { + case UserData.ENCODING_UNICODE_16: + encodedBody = body.getBytes("UTF-16BE"); + break; + + case UserData.ENCODING_SHIFT_JIS: + encodedBody = body.getBytes("Shift_JIS"); + break; + + case UserData.ENCODING_KOREAN: + encodedBody = body.getBytes("KSC5601"); + break; + + case UserData.ENCODING_LATIN_HEBREW: + encodedBody = body.getBytes("ISO-8859-8"); + break; + + case UserData.ENCODING_LATIN: + default: + encodedBody = body.getBytes("ISO-8859-1"); + break; + } + int charCount = body.length(); // use actual char count for num fields + int recordOctets = encodedBody.length + 1; // add 1 byte for encoding and pad bits + if (!isCmasRecord) { + recordOctets++; // add 8 bits for num_fields + } + bos.write(8, recordOctets); + bos.write(5, (encoding & 0x1f)); + if (!isCmasRecord) { + bos.write(8, charCount); + } + bos.writeByteArray(encodedBody.length * 8, encodedBody); + bos.write(3, 0); // pad to octet boundary + } + } + + private static final String TEST_TEXT = "This is a test CDMA cell broadcast message..." + + "678901234567890123456789012345678901234567890"; + + private static final String PRES_ALERT = + "THE PRESIDENT HAS ISSUED AN EMERGENCY ALERT. CHECK LOCAL MEDIA FOR MORE DETAILS"; + + private static final String EXTREME_ALERT = "FLASH FLOOD WARNING FOR SOUTH COCONINO COUNTY" + + " - NORTH CENTRAL ARIZONA UNTIL 415 PM MST"; + + private static final String SEVERE_ALERT = "SEVERE WEATHER WARNING FOR SOMERSET COUNTY" + + " - NEW JERSEY UNTIL 415 PM MST"; + + private static final String AMBER_ALERT = + "AMBER ALERT:Mountain View,CA VEH'07 Blue Honda Civic CA LIC 5ABC123"; + + private static final String MONTHLY_TEST_ALERT = "This is a test of the emergency alert system." + + " This is only a test. 89012345678901234567890"; + + private static final String IS91_TEXT = "IS91 SHORT MSG"; // max length 14 chars + + /** + * Verify that the SmsCbMessage has the correct values for CDMA. + * @param cbMessage the message to test + */ + private static void verifyCbValues(SmsCbMessage cbMessage) { + assertEquals(SmsCbMessage.MESSAGE_FORMAT_3GPP2, cbMessage.getMessageFormat()); + assertEquals(SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, cbMessage.getGeographicalScope()); + assertEquals(false, cbMessage.isEtwsMessage()); // ETWS on CDMA not currently supported + } + + private static void doTestNonEmergencyBroadcast(int encoding) throws Exception { + SmsMessage msg = createBroadcastSmsMessage(123, 456, BearerData.PRIORITY_NORMAL, + BearerData.LANGUAGE_ENGLISH, encoding, TEST_TEXT); + + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + verifyCbValues(cbMessage); + assertEquals(123, cbMessage.getServiceCategory()); + assertEquals(456, cbMessage.getSerialNumber()); + assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority()); + assertEquals("en", cbMessage.getLanguageCode()); + assertEquals(TEST_TEXT, cbMessage.getMessageBody()); + assertEquals(false, cbMessage.isEmergencyMessage()); + assertEquals(false, cbMessage.isCmasMessage()); + } + + public void testNonEmergencyBroadcast7bitAscii() throws Exception { + doTestNonEmergencyBroadcast(UserData.ENCODING_7BIT_ASCII); + } + + public void testNonEmergencyBroadcast7bitGsm() throws Exception { + doTestNonEmergencyBroadcast(UserData.ENCODING_GSM_7BIT_ALPHABET); + } + + public void testNonEmergencyBroadcast16bitUnicode() throws Exception { + doTestNonEmergencyBroadcast(UserData.ENCODING_UNICODE_16); + } + + public void testNonEmergencyBroadcastIs91Extended() throws Exception { + // IS-91 doesn't support language or priority subparameters, max 14 chars text + SmsMessage msg = createBroadcastSmsMessage(987, 654, -1, -1, + UserData.ENCODING_IS91_EXTENDED_PROTOCOL, IS91_TEXT); + + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + verifyCbValues(cbMessage); + assertEquals(987, cbMessage.getServiceCategory()); + assertEquals(654, cbMessage.getSerialNumber()); + assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority()); + assertEquals(null, cbMessage.getLanguageCode()); + assertEquals(IS91_TEXT, cbMessage.getMessageBody()); + assertEquals(false, cbMessage.isEmergencyMessage()); + assertEquals(false, cbMessage.isCmasMessage()); + } + + private static void doTestCmasBroadcast(int serviceCategory, int messageClass, String body) + throws Exception { + SmsMessage msg = createCmasSmsMessage( + serviceCategory, 1234, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, + UserData.ENCODING_7BIT_ASCII, body, -1, -1, -1, -1, -1); + + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + verifyCbValues(cbMessage); + assertEquals(serviceCategory, cbMessage.getServiceCategory()); + assertEquals(1234, cbMessage.getSerialNumber()); + assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority()); + assertEquals("en", cbMessage.getLanguageCode()); + assertEquals(body, cbMessage.getMessageBody()); + assertEquals(true, cbMessage.isEmergencyMessage()); + assertEquals(true, cbMessage.isCmasMessage()); + SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo(); + assertEquals(messageClass, cmasInfo.getMessageClass()); + assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory()); + assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType()); + assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity()); + assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency()); + assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty()); + } + + public void testCmasPresidentialAlert() throws Exception { + doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, + SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, PRES_ALERT); + } + + public void testCmasExtremeAlert() throws Exception { + doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, + SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, EXTREME_ALERT); + } + + public void testCmasSevereAlert() throws Exception { + doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, + SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT, SEVERE_ALERT); + } + + public void testCmasAmberAlert() throws Exception { + doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, + SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY, AMBER_ALERT); + } + + public void testCmasTestMessage() throws Exception { + doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE, + SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST, MONTHLY_TEST_ALERT); + } + + public void testCmasExtremeAlertType1Elements() throws Exception { + SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, + 5678, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, + UserData.ENCODING_7BIT_ASCII, EXTREME_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_ENV, + SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, SmsCbCmasInfo.CMAS_SEVERITY_SEVERE, + SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY); + + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + verifyCbValues(cbMessage); + assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, + cbMessage.getServiceCategory()); + assertEquals(5678, cbMessage.getSerialNumber()); + assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority()); + assertEquals("en", cbMessage.getLanguageCode()); + assertEquals(EXTREME_ALERT, cbMessage.getMessageBody()); + assertEquals(true, cbMessage.isEmergencyMessage()); + assertEquals(true, cbMessage.isCmasMessage()); + SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo(); + assertEquals(SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, cmasInfo.getMessageClass()); + assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_ENV, cmasInfo.getCategory()); + assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, cmasInfo.getResponseType()); + assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_SEVERE, cmasInfo.getSeverity()); + assertEquals(SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, cmasInfo.getUrgency()); + assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY, cmasInfo.getCertainty()); + } + + // VZW requirement is to discard message with unsupported charset. Verify that we return null + // for this unsupported character set. + public void testCmasUnsupportedCharSet() throws Exception { + SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, + 12345, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, + UserData.ENCODING_GSM_DCS, EXTREME_ALERT, -1, -1, -1, -1, -1); + + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + assertNull("expected null for unsupported charset", cbMessage); + } + + // VZW requirement is to discard message with unsupported charset. Verify that we return null + // for this unsupported character set. + public void testCmasUnsupportedCharSet2() throws Exception { + SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, + 67890, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, + UserData.ENCODING_KOREAN, EXTREME_ALERT, -1, -1, -1, -1, -1); + + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + assertNull("expected null for unsupported charset", cbMessage); + } + + // VZW requirement is to discard message without record type 0. The framework will decode it + // and the app will discard it. + public void testCmasNoRecordType0() throws Exception { + SmsMessage msg = createCmasSmsMessage( + SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 1234, + BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH, + UserData.ENCODING_7BIT_ASCII, null, -1, -1, -1, -1, -1); + + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + verifyCbValues(cbMessage); + assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, + cbMessage.getServiceCategory()); + assertEquals(1234, cbMessage.getSerialNumber()); + assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority()); + assertEquals("en", cbMessage.getLanguageCode()); + assertEquals(null, cbMessage.getMessageBody()); + assertEquals(true, cbMessage.isEmergencyMessage()); + assertEquals(true, cbMessage.isCmasMessage()); + SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo(); + assertEquals(SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, cmasInfo.getMessageClass()); + assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory()); + assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType()); + assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity()); + assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency()); + assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty()); + } + + // Make sure we don't throw an exception if we feed completely random data to BearerStream. + public void testRandomBearerStreamData() { + Random r = new Random(54321); + for (int run = 0; run < 1000; run++) { + int len = r.nextInt(140); + byte[] data = new byte[len]; + for (int i = 0; i < len; i++) { + data[i] = (byte) r.nextInt(256); + } + // Log.d("CdmaSmsCbTest", "trying random bearer data run " + run + " length " + len); + try { + int category = 0x0ff0 + r.nextInt(32); // half CMAS, half non-CMAS + Parcel p = createBroadcastParcel(category); + SmsMessage msg = createMessageFromParcel(p, data); + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + // with random input, cbMessage will almost always be null (log when it isn't) + if (cbMessage != null) { + Log.d("CdmaSmsCbTest", "success: " + cbMessage); + } + } catch (Exception e) { + Log.d("CdmaSmsCbTest", "exception thrown", e); + fail("Exception in decoder at run " + run + " length " + len + ": " + e); + } + } + } + + // Make sure we don't throw an exception if we put random data in the UserData subparam. + public void testRandomUserData() { + Random r = new Random(94040); + for (int run = 0; run < 1000; run++) { + int category = 0x0ff0 + r.nextInt(32); // half CMAS, half non-CMAS + Parcel p = createBroadcastParcel(category); + int len = r.nextInt(140); + // Log.d("CdmaSmsCbTest", "trying random user data run " + run + " length " + len); + + try { + BitwiseOutputStream bos = createBearerDataStream(r.nextInt(65536), r.nextInt(4), + r.nextInt(256)); + + bos.write(8, SUBPARAM_USER_DATA); + bos.write(8, len); + + for (int i = 0; i < len; i++) { + bos.write(8, r.nextInt(256)); + } + + SmsMessage msg = createMessageFromParcel(p, bos.toByteArray()); + SmsCbMessage cbMessage = msg.parseBroadcastSms(); + } catch (Exception e) { + Log.d("CdmaSmsCbTest", "exception thrown", e); + fail("Exception in decoder at run " + run + " length " + len + ": " + e); + } + } + } + + /** + * Initialize a Parcel for incoming Service Category Program Data teleservice. The caller will + * write the bearer data and then convert it to an SmsMessage. + * @return the initialized Parcel + */ + private static Parcel createServiceCategoryProgramDataParcel() { + Parcel p = Parcel.obtain(); + + p.writeInt(SmsEnvelope.TELESERVICE_SCPT); + p.writeByte((byte) 0); // non-zero for MESSAGE_TYPE_BROADCAST + p.writeInt(0); + + // dummy address (RIL may generate a different dummy address for broadcasts) + p.writeInt(CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF); // sAddress.digit_mode + p.writeInt(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK); // sAddress.number_mode + p.writeInt(CdmaSmsAddress.TON_UNKNOWN); // sAddress.number_type + p.writeInt(CdmaSmsAddress.NUMBERING_PLAN_ISDN_TELEPHONY); // sAddress.number_plan + p.writeByte((byte) 0); // sAddress.number_of_digits + p.writeInt((byte) 0); // sSubAddress.subaddressType + p.writeByte((byte) 0); // sSubAddress.odd + p.writeByte((byte) 0); // sSubAddress.number_of_digits + return p; + } + + private static final String CAT_EXTREME_THREAT = "Extreme Threat to Life and Property"; + private static final String CAT_SEVERE_THREAT = "Severe Threat to Life and Property"; + private static final String CAT_AMBER_ALERTS = "AMBER Alerts"; + + public void testServiceCategoryProgramDataAddCategory() throws Exception { + Parcel p = createServiceCategoryProgramDataParcel(); + BitwiseOutputStream bos = createBearerDataStream(123, -1, -1); + + int categoryNameLength = CAT_EXTREME_THREAT.length(); + int subparamLengthBits = (53 + (categoryNameLength * 7)); + int subparamLengthBytes = (subparamLengthBits + 7) / 8; + int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits; + + bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA); + bos.write(8, subparamLengthBytes); + bos.write(5, UserData.ENCODING_7BIT_ASCII); + + bos.write(4, CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY); + bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT >>> 8)); + bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT & 0xff)); + bos.write(8, BearerData.LANGUAGE_ENGLISH); + bos.write(8, 100); // max messages + bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT); + + bos.write(8, categoryNameLength); + for (int i = 0; i < categoryNameLength; i++) { + bos.write(7, CAT_EXTREME_THREAT.charAt(i)); + } + bos.write(subparamPadBits, 0); + + SmsMessage msg = createMessageFromParcel(p, bos.toByteArray()); + assertNotNull(msg); + msg.parseSms(); + List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData(); + assertNotNull(programDataList); + assertEquals(1, programDataList.size()); + CdmaSmsCbProgramData programData = programDataList.get(0); + assertEquals(CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY, programData.getOperation()); + assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, programData.getCategory()); + assertEquals(CAT_EXTREME_THREAT, programData.getCategoryName()); + assertEquals("en", programData.getLanguageCode()); + assertEquals(100, programData.getMaxMessages()); + assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT, programData.getAlertOption()); + } + + public void testServiceCategoryProgramDataDeleteTwoCategories() throws Exception { + Parcel p = createServiceCategoryProgramDataParcel(); + BitwiseOutputStream bos = createBearerDataStream(456, -1, -1); + + int category1NameLength = CAT_SEVERE_THREAT.length(); + int category2NameLength = CAT_AMBER_ALERTS.length(); + + int subparamLengthBits = (101 + (category1NameLength * 7) + (category2NameLength * 7)); + int subparamLengthBytes = (subparamLengthBits + 7) / 8; + int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits; + + bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA); + bos.write(8, subparamLengthBytes); + bos.write(5, UserData.ENCODING_7BIT_ASCII); + + bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY); + bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT >>> 8)); + bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT & 0xff)); + bos.write(8, BearerData.LANGUAGE_ENGLISH); + bos.write(8, 0); // max messages + bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT); + + bos.write(8, category1NameLength); + for (int i = 0; i < category1NameLength; i++) { + bos.write(7, CAT_SEVERE_THREAT.charAt(i)); + } + + bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY); + bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY >>> 8)); + bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY & 0xff)); + bos.write(8, BearerData.LANGUAGE_ENGLISH); + bos.write(8, 0); // max messages + bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT); + + bos.write(8, category2NameLength); + for (int i = 0; i < category2NameLength; i++) { + bos.write(7, CAT_AMBER_ALERTS.charAt(i)); + } + + bos.write(subparamPadBits, 0); + + SmsMessage msg = createMessageFromParcel(p, bos.toByteArray()); + assertNotNull(msg); + msg.parseSms(); + List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData(); + assertNotNull(programDataList); + assertEquals(2, programDataList.size()); + + CdmaSmsCbProgramData programData = programDataList.get(0); + assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation()); + assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, programData.getCategory()); + assertEquals(CAT_SEVERE_THREAT, programData.getCategoryName()); + assertEquals("en", programData.getLanguageCode()); + assertEquals(0, programData.getMaxMessages()); + assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption()); + + programData = programDataList.get(1); + assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation()); + assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, + programData.getCategory()); + assertEquals(CAT_AMBER_ALERTS, programData.getCategoryName()); + assertEquals("en", programData.getLanguageCode()); + assertEquals(0, programData.getMaxMessages()); + assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption()); + } +} diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java index 417aac4cca92..82c6944e13f6 100644 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java @@ -14,34 +14,54 @@ * limitations under the License. */ -package com.android.internal.telephony; +package com.android.internal.telephony.gsm; +import android.telephony.SmsCbEtwsInfo; +import android.telephony.SmsCbLocation; import android.telephony.SmsCbMessage; import android.test.AndroidTestCase; import android.util.Log; +import com.android.internal.telephony.IccUtils; + +import java.util.Random; + /** * Test cases for basic SmsCbMessage operations */ public class GsmSmsCbTest extends AndroidTestCase { - private void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) { + private static final String TAG = "GsmSmsCbTest"; + + private static final SmsCbLocation sTestLocation = new SmsCbLocation("94040", 1234, 5678); + + private static SmsCbMessage createFromPdu(byte[] pdu) { + try { + SmsCbHeader header = new SmsCbHeader(pdu); + byte[][] pdus = new byte[1][]; + pdus[0] = pdu; + return GsmSmsCbMessage.createSmsCbMessage(header, sTestLocation, pdus); + } catch (IllegalArgumentException e) { + return null; + } + } + + private static void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) { pdu[0] = b; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected geographical scope decoded", expectedGs, msg .getGeographicalScope()); } public void testCreateNullPdu() { - SmsCbMessage msg = SmsCbMessage.createFromPdu(null); - + SmsCbMessage msg = createFromPdu(null); assertNull("createFromPdu(byte[] with null pdu should return null", msg); } public void testCreateTooShortPdu() { byte[] pdu = new byte[4]; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertNull("createFromPdu(byte[] with too short pdu should return null", msg); } @@ -94,7 +114,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected geographical scope decoded", SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE, msg.getGeographicalScope()); @@ -116,7 +136,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -146,7 +166,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -193,7 +213,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x0A }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected multipage 7-bit string decoded", "First page+Second page", @@ -216,7 +236,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x90, (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4, (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals( "Unexpected 7-bit string decoded", @@ -248,7 +268,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x52 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals( "Unexpected 7-bit string decoded", @@ -273,7 +293,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -298,7 +318,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D, (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -330,7 +350,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x37 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A GSM default alphabet message with carriage return padding", @@ -355,7 +375,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41, (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("8-bit message body should be empty", "", msg.getMessageBody()); } @@ -376,7 +396,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A UCS2 message containing a \u0434 character", msg.getMessageBody()); @@ -405,7 +425,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x4E }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A UCS2 message containing a \u0434 character", msg.getMessageBody()); @@ -451,7 +471,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x06 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected multipage UCS2 string decoded", "AAABBB", msg.getMessageBody()); @@ -473,7 +493,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x61, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A UCS2 message containing a \u0434 character", msg.getMessageBody()); @@ -504,7 +524,7 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x50 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); assertEquals("Unexpected 7-bit string decoded", "A UCS2 message containing a \u0434 character", msg.getMessageBody()); @@ -529,9 +549,9 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); - assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier()); + assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory()); } public void testGetMessageIdentifierUmts() { @@ -558,9 +578,9 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); - assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier()); + assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory()); } public void testGetMessageCode() { @@ -580,9 +600,10 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); + int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4; - assertEquals("Unexpected message code decoded", 682, msg.getMessageCode()); + assertEquals("Unexpected message code decoded", 682, messageCode); } public void testGetMessageCodeUmts() { @@ -609,9 +630,10 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); + int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4; - assertEquals("Unexpected message code decoded", 682, msg.getMessageCode()); + assertEquals("Unexpected message code decoded", 682, messageCode); } public void testGetUpdateNumber() { @@ -631,9 +653,10 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); + int updateNumber = msg.getSerialNumber() & 0x000f; - assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber()); + assertEquals("Unexpected update number decoded", 5, updateNumber); } public void testGetUpdateNumberUmts() { @@ -660,9 +683,10 @@ public class GsmSmsCbTest extends AndroidTestCase { (byte)0x34 }; - SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu); + SmsCbMessage msg = createFromPdu(pdu); + int updateNumber = msg.getSerialNumber() & 0x000f; - assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber()); + assertEquals("Unexpected update number decoded", 5, updateNumber); } /* ETWS Test message including header */ @@ -684,29 +708,51 @@ public class GsmSmsCbTest extends AndroidTestCase { // FIXME: add example of ETWS primary notification PDU public void testEtwsMessageNormal() { - SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageNormal); - Log.d("GsmSmsCbTest", msg.toString()); + SmsCbMessage msg = createFromPdu(etwsMessageNormal); + Log.d(TAG, msg.toString()); assertEquals("GS mismatch", 0, msg.getGeographicalScope()); - assertEquals("message code mismatch", 0, msg.getMessageCode()); - assertEquals("update number mismatch", 0, msg.getUpdateNumber()); - assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier()); + assertEquals("serial number mismatch", 0, msg.getSerialNumber()); + assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory()); + assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE, + msg.getEtwsWarningInfo().getWarningType()); } public void testEtwsMessageCancel() { - SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageCancel); - Log.d("GsmSmsCbTest", msg.toString()); + SmsCbMessage msg = createFromPdu(etwsMessageCancel); + Log.d(TAG, msg.toString()); assertEquals("GS mismatch", 0, msg.getGeographicalScope()); - assertEquals("message code mismatch", 0, msg.getMessageCode()); - assertEquals("update number mismatch", 0, msg.getUpdateNumber()); - assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier()); + assertEquals("serial number mismatch", 0, msg.getSerialNumber()); + assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory()); + assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE, + msg.getEtwsWarningInfo().getWarningType()); } public void testEtwsMessageTest() { - SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageTest); - Log.d("GsmSmsCbTest", msg.toString()); + SmsCbMessage msg = createFromPdu(etwsMessageTest); + Log.d(TAG, msg.toString()); assertEquals("GS mismatch", 0, msg.getGeographicalScope()); - assertEquals("message code mismatch", 0, msg.getMessageCode()); - assertEquals("update number mismatch", 0, msg.getUpdateNumber()); - assertEquals("message ID mismatch", 0x1103, msg.getMessageIdentifier()); + assertEquals("serial number mismatch", 0, msg.getSerialNumber()); + assertEquals("message ID mismatch", 0x1103, msg.getServiceCategory()); + assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE, + msg.getEtwsWarningInfo().getWarningType()); + } + + // Make sure we don't throw an exception if we feed random data to the PDU parser. + public void testRandomPdus() { + Random r = new Random(94040); + for (int run = 0; run < 10000; run++) { + int len = r.nextInt(140); + byte[] data = new byte[len]; + for (int i = 0; i < len; i++) { + data[i] = (byte) r.nextInt(256); + } + try { + // this should return a SmsCbMessage object or null for invalid data + SmsCbMessage msg = createFromPdu(data); + } catch (Exception e) { + Log.d(TAG, "exception thrown", e); + fail("Exception in decoder at run " + run + " length " + len + ": " + e); + } + } } } diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 5fab2bb2d65d..bf583e1ca774 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -251,16 +251,33 @@ public class MockContext extends Context { } @Override + public void startActivity(Intent intent, Bundle options) { + startActivity(intent); + } + + @Override public void startActivities(Intent[] intents) { throw new UnsupportedOperationException(); } @Override + public void startActivities(Intent[] intents, Bundle options) { + startActivities(intents); + } + + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { throw new UnsupportedOperationException(); } + + @Override + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException { + startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags); + } @Override public void sendBroadcast(Intent intent) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 58dc2d4984e6..6c49bab86146 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1234,7 +1234,11 @@ public final class BridgeContext extends Context { @Override public void startActivity(Intent arg0) { // TODO Auto-generated method stub + } + @Override + public void startActivity(Intent arg0, Bundle arg1) { + // TODO Auto-generated method stub } @Override @@ -1245,6 +1249,13 @@ public final class BridgeContext extends Context { } @Override + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, + Bundle options) throws IntentSender.SendIntentException { + // TODO Auto-generated method stub + } + + @Override public boolean startInstrumentation(ComponentName arg0, String arg1, Bundle arg2) { // TODO Auto-generated method stub @@ -1287,6 +1298,12 @@ public final class BridgeContext extends Context { } @Override + public void startActivities(Intent[] arg0, Bundle arg1) { + // TODO Auto-generated method stub + + } + + @Override public boolean isRestricted() { return false; } |