Merge "Restrict access from apps to bluetooth_address setting"
diff --git a/Android.mk b/Android.mk
index f318a5a..6d797ace 100644
--- a/Android.mk
+++ b/Android.mk
@@ -37,6 +37,9 @@
include $(CLEAR_VARS)
+# Load framework-specific path mappings used later in the build.
+include $(LOCAL_PATH)/pathmap.mk
+
# FRAMEWORKS_BASE_SUBDIRS comes from build/core/pathmap.mk
LOCAL_SRC_FILES := \
$(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS)) \
diff --git a/api/current.txt b/api/current.txt
index 8857abc..b422a71 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -298,6 +298,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillHint = 16844121; // 0x1010559
field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -5519,12 +5520,14 @@
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor public NotificationChannelGroup(java.lang.String, int);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public android.app.NotificationChannelGroup clone();
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
@@ -6554,6 +6557,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
+ method public int getAutoFillHint();
method public android.view.autofill.AutoFillId getAutoFillId();
method public java.lang.String[] getAutoFillOptions();
method public android.view.autofill.AutoFillType getAutoFillType();
@@ -7000,7 +7004,7 @@
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
- method public boolean requestPinAppWidget(android.content.ComponentName, android.app.PendingIntent);
+ method public boolean requestPinAppWidget(android.content.ComponentName, android.os.Bundle, android.app.PendingIntent);
method public void updateAppWidget(int[], android.widget.RemoteViews);
method public void updateAppWidget(int, android.widget.RemoteViews);
method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -7019,6 +7023,7 @@
field public static final java.lang.String EXTRA_APPWIDGET_IDS = "appWidgetIds";
field public static final java.lang.String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
field public static final java.lang.String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions";
+ field public static final java.lang.String EXTRA_APPWIDGET_PREVIEW = "appWidgetPreview";
field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile";
field public static final java.lang.String EXTRA_CUSTOM_EXTRAS = "customExtras";
@@ -10102,6 +10107,7 @@
method public boolean accept();
method public int describeContents();
method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo(android.content.Context);
+ method public android.os.Bundle getExtras();
method public int getRequestType();
method public android.content.pm.ShortcutInfo getShortcutInfo();
method public boolean isValid();
@@ -44823,6 +44829,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillHint();
method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -45140,6 +45147,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillHint(int);
method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
@@ -45281,6 +45289,20 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+ field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+ field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+ field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+ field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+ field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+ field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+ field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+ field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -45933,6 +45955,7 @@
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillHint(int);
method public abstract void setAutoFillOptions(java.lang.String[]);
method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -47204,9 +47227,8 @@
method public int describeContents();
method public static android.view.autofill.AutoFillType forDate();
method public static android.view.autofill.AutoFillType forList();
- method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forText();
method public static android.view.autofill.AutoFillType forToggle();
- method public int getSubType();
method public boolean isDate();
method public boolean isList();
method public boolean isText();
diff --git a/api/system-current.txt b/api/system-current.txt
index 557b54b..234a2d6 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -410,6 +410,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillHint = 16844121; // 0x1010559
field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -5712,6 +5713,7 @@
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor public NotificationChannelGroup(java.lang.String, int);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public void addChannel(android.app.NotificationChannel);
method public android.app.NotificationChannelGroup clone();
@@ -5719,6 +5721,7 @@
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public org.json.JSONObject toJson() throws org.json.JSONException;
method public void writeToParcel(android.os.Parcel, int);
method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
@@ -6793,6 +6796,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
+ method public int getAutoFillHint();
method public android.view.autofill.AutoFillId getAutoFillId();
method public java.lang.String[] getAutoFillOptions();
method public android.view.autofill.AutoFillType getAutoFillType();
@@ -7460,7 +7464,7 @@
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
- method public boolean requestPinAppWidget(android.content.ComponentName, android.app.PendingIntent);
+ method public boolean requestPinAppWidget(android.content.ComponentName, android.os.Bundle, android.app.PendingIntent);
method public void updateAppWidget(int[], android.widget.RemoteViews);
method public void updateAppWidget(int, android.widget.RemoteViews);
method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -7479,6 +7483,7 @@
field public static final java.lang.String EXTRA_APPWIDGET_IDS = "appWidgetIds";
field public static final java.lang.String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
field public static final java.lang.String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions";
+ field public static final java.lang.String EXTRA_APPWIDGET_PREVIEW = "appWidgetPreview";
field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile";
field public static final java.lang.String EXTRA_CUSTOM_EXTRAS = "customExtras";
@@ -9680,6 +9685,7 @@
field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT";
field public static final java.lang.String ACTION_USER_UNLOCKED = "android.intent.action.USER_UNLOCKED";
field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW";
+ field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
field public static final java.lang.String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -10676,6 +10682,7 @@
method public boolean accept();
method public int describeContents();
method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo(android.content.Context);
+ method public android.os.Bundle getExtras();
method public int getRequestType();
method public android.content.pm.ShortcutInfo getShortcutInfo();
method public boolean isValid();
@@ -48209,6 +48216,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillHint();
method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -48526,6 +48534,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillHint(int);
method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
@@ -48667,6 +48676,20 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+ field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+ field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+ field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+ field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+ field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+ field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+ field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+ field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -49319,6 +49342,7 @@
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillHint(int);
method public abstract void setAutoFillOptions(java.lang.String[]);
method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -50593,9 +50617,8 @@
method public int describeContents();
method public static android.view.autofill.AutoFillType forDate();
method public static android.view.autofill.AutoFillType forList();
- method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forText();
method public static android.view.autofill.AutoFillType forToggle();
- method public int getSubType();
method public boolean isDate();
method public boolean isList();
method public boolean isText();
diff --git a/api/test-current.txt b/api/test-current.txt
index e542ee4..f7570a3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -298,6 +298,7 @@
field public static final int authorities = 16842776; // 0x1010018
field public static final int autoAdvanceViewId = 16843535; // 0x101030f
field public static final int autoCompleteTextViewStyle = 16842859; // 0x101006b
+ field public static final int autoFillHint = 16844121; // 0x1010559
field public static final int autoFillMode = 16844116; // 0x1010554
field public static final int autoLink = 16842928; // 0x10100b0
field public static final int autoMirrored = 16843754; // 0x10103ea
@@ -5529,12 +5530,14 @@
public final class NotificationChannelGroup implements android.os.Parcelable {
ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence);
+ ctor public NotificationChannelGroup(java.lang.String, int);
ctor protected NotificationChannelGroup(android.os.Parcel);
method public android.app.NotificationChannelGroup clone();
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
@@ -6264,6 +6267,7 @@
method public long getMaximumTimeToLock(android.content.ComponentName);
method public int getOrganizationColor(android.content.ComponentName);
method public java.lang.CharSequence getOrganizationName(android.content.ComponentName);
+ method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(android.os.UserHandle);
method public android.app.admin.DevicePolicyManager getParentProfileInstance(android.content.ComponentName);
method public long getPasswordExpiration(android.content.ComponentName);
method public long getPasswordExpirationTimeout(android.content.ComponentName);
@@ -6580,6 +6584,7 @@
public static class AssistStructure.ViewNode {
method public float getAlpha();
+ method public int getAutoFillHint();
method public android.view.autofill.AutoFillId getAutoFillId();
method public java.lang.String[] getAutoFillOptions();
method public android.view.autofill.AutoFillType getAutoFillType();
@@ -7026,7 +7031,7 @@
method public void notifyAppWidgetViewDataChanged(int, int);
method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
- method public boolean requestPinAppWidget(android.content.ComponentName, android.app.PendingIntent);
+ method public boolean requestPinAppWidget(android.content.ComponentName, android.os.Bundle, android.app.PendingIntent);
method public void updateAppWidget(int[], android.widget.RemoteViews);
method public void updateAppWidget(int, android.widget.RemoteViews);
method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -7045,6 +7050,7 @@
field public static final java.lang.String EXTRA_APPWIDGET_IDS = "appWidgetIds";
field public static final java.lang.String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
field public static final java.lang.String EXTRA_APPWIDGET_OPTIONS = "appWidgetOptions";
+ field public static final java.lang.String EXTRA_APPWIDGET_PREVIEW = "appWidgetPreview";
field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER = "appWidgetProvider";
field public static final java.lang.String EXTRA_APPWIDGET_PROVIDER_PROFILE = "appWidgetProviderProfile";
field public static final java.lang.String EXTRA_CUSTOM_EXTRAS = "customExtras";
@@ -10134,6 +10140,7 @@
method public boolean accept();
method public int describeContents();
method public android.appwidget.AppWidgetProviderInfo getAppWidgetProviderInfo(android.content.Context);
+ method public android.os.Bundle getExtras();
method public int getRequestType();
method public android.content.pm.ShortcutInfo getShortcutInfo();
method public boolean isValid();
@@ -45178,6 +45185,7 @@
method public float getAlpha();
method public android.view.animation.Animation getAnimation();
method public android.os.IBinder getApplicationWindowToken();
+ method public int getAutoFillHint();
method public int getAutoFillMode();
method public android.view.autofill.AutoFillType getAutoFillType();
method public android.view.autofill.AutoFillValue getAutoFillValue();
@@ -45498,6 +45506,7 @@
method public void setActivated(boolean);
method public void setAlpha(float);
method public void setAnimation(android.view.animation.Animation);
+ method public void setAutoFillHint(int);
method public void setAutoFillMode(int);
method public void setBackground(android.graphics.drawable.Drawable);
method public void setBackgroundColor(int);
@@ -45639,6 +45648,20 @@
field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA;
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80
+ field public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100
+ field public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 1; // 0x1
+ field public static final int AUTO_FILL_HINT_NAME = 2; // 0x2
+ field public static final int AUTO_FILL_HINT_NONE = 0; // 0x0
+ field public static final int AUTO_FILL_HINT_PASSWORD = 8; // 0x8
+ field public static final int AUTO_FILL_HINT_PHONE = 16; // 0x10
+ field public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 32; // 0x20
+ field public static final int AUTO_FILL_HINT_POSTAL_CODE = 64; // 0x40
+ field public static final int AUTO_FILL_HINT_USERNAME = 4; // 0x4
field public static final int AUTO_FILL_MODE_AUTO = 1; // 0x1
field public static final int AUTO_FILL_MODE_INHERIT = 0; // 0x0
field public static final int AUTO_FILL_MODE_MANUAL = 2; // 0x2
@@ -46295,6 +46318,7 @@
method public abstract void setAccessibilityFocused(boolean);
method public abstract void setActivated(boolean);
method public abstract void setAlpha(float);
+ method public abstract void setAutoFillHint(int);
method public abstract void setAutoFillOptions(java.lang.String[]);
method public abstract void setAutoFillType(android.view.autofill.AutoFillType);
method public abstract void setAutoFillValue(android.view.autofill.AutoFillValue);
@@ -47568,9 +47592,8 @@
method public int describeContents();
method public static android.view.autofill.AutoFillType forDate();
method public static android.view.autofill.AutoFillType forList();
- method public static android.view.autofill.AutoFillType forText(int);
+ method public static android.view.autofill.AutoFillType forText();
method public static android.view.autofill.AutoFillType forToggle();
- method public int getSubType();
method public boolean isDate();
method public boolean isList();
method public boolean isText();
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8538330..dad2061 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -522,6 +522,7 @@
void startLocalVoiceInteraction(in IBinder token, in Bundle options);
void stopLocalVoiceInteraction(in IBinder token);
boolean supportsLocalVoiceInteraction();
+ void notifyPinnedStackAnimationStarted();
void notifyPinnedStackAnimationEnded();
void removeStack(int stackId);
void makePackageIdle(String packageName, int userId);
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 9834350..d8d4bb9 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -38,6 +38,11 @@
void onPinnedActivityRestartAttempt(String launchedFromPackage);
/**
+ * Called whenever the pinned stack is starting animating a resize.
+ */
+ void onPinnedStackAnimationStarted();
+
+ /**
* Called whenever the pinned stack is done animating a resize.
*/
void onPinnedStackAnimationEnded();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 1c33e38..7cdd45f 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -286,7 +286,7 @@
final String apkName = path.substring(path.lastIndexOf(File.separator));
boolean match = false;
for (String oldPath : oldPaths) {
- final String oldApkName = oldPath.substring(path.lastIndexOf(File.separator));
+ final String oldApkName = oldPath.substring(oldPath.lastIndexOf(File.separator));
if (apkName.equals(oldApkName)) {
match = true;
break;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 34a45dd6..3fc459e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4177,7 +4177,7 @@
}
RemoteViews header = makeNotificationHeader();
-
+ header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true);
if (summary != null) {
mN.extras.putCharSequence(EXTRA_SUB_TEXT, summary);
} else {
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 8854adc..288d39a 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -15,6 +15,7 @@
*/
package android.app;
+import android.annotation.StringRes;
import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Parcel;
@@ -40,23 +41,38 @@
private static final String TAG_GROUP = "channelGroup";
private static final String ATT_NAME = "name";
+ private static final String ATT_NAME_RES_ID = "name_res_id";
private static final String ATT_ID = "id";
private final String mId;
private CharSequence mName;
+ private int mNameResId = 0;
private List<NotificationChannel> mChannels = new ArrayList<>();
/**
* Creates a notification channel.
*
* @param id The id of the group. Must be unique per package.
- * @param name The user visible name of the group.
+ * @param name The user visible name of the group. Unchangeable once created; use this
+ * constructor if the group represents something user-defined that does not
+ * need to be translated.
*/
public NotificationChannelGroup(String id, CharSequence name) {
this.mId = id;
this.mName = name;
}
+ /**
+ * Creates a notification channel.
+ *
+ * @param id The id of the group. Must be unique per package.
+ * @param nameResId String resource id of the user visible name of the group.
+ */
+ public NotificationChannelGroup(String id, @StringRes int nameResId) {
+ this.mId = id;
+ this.mNameResId = nameResId;
+ }
+
protected NotificationChannelGroup(Parcel in) {
if (in.readByte() != 0) {
mId = in.readString();
@@ -64,6 +80,7 @@
mId = null;
}
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mNameResId = in.readInt();
in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
}
@@ -76,6 +93,7 @@
dest.writeByte((byte) 0);
}
TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeInt(mNameResId);
dest.writeParcelableList(mChannels, flags);
}
@@ -93,6 +111,13 @@
return mName;
}
+ /**
+ * Returns the resource id of the user visible name of this group.
+ */
+ public @StringRes int getNameResId() {
+ return mNameResId;
+ }
+
/*
* Returns the list of channels that belong to this group
*
@@ -119,7 +144,12 @@
out.startTag(null, TAG_GROUP);
out.attribute(null, ATT_ID, getId());
- out.attribute(null, ATT_NAME, getName().toString());
+ if (getName() != null) {
+ out.attribute(null, ATT_NAME, getName().toString());
+ }
+ if (getNameResId() != 0) {
+ out.attribute(null, ATT_NAME_RES_ID, Integer.toString(getNameResId()));
+ }
out.endTag(null, TAG_GROUP);
}
@@ -132,6 +162,7 @@
JSONObject record = new JSONObject();
record.put(ATT_ID, getId());
record.put(ATT_NAME, getName());
+ record.put(ATT_NAME_RES_ID, getNameResId());
return record;
}
@@ -160,6 +191,7 @@
NotificationChannelGroup that = (NotificationChannelGroup) o;
+ if (getNameResId() != that.getNameResId()) return false;
if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
return false;
@@ -171,13 +203,18 @@
@Override
public NotificationChannelGroup clone() {
- return new NotificationChannelGroup(getId(), getName());
+ if (getName() != null) {
+ return new NotificationChannelGroup(getId(), getName());
+ } else {
+ return new NotificationChannelGroup(getId(), getNameResId());
+ }
}
@Override
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+ result = 31 * result + getNameResId();
result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0);
return result;
}
@@ -187,6 +224,8 @@
return "NotificationChannelGroup{" +
"mId='" + mId + '\'' +
", mName=" + mName +
+ ", mNameResId=" + mNameResId +
+ ", mChannels=" + mChannels +
'}';
}
}
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index 5a0845f..7a569fc 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -39,6 +39,10 @@
}
@Override
+ public void onPinnedStackAnimationStarted() throws RemoteException {
+ }
+
+ @Override
public void onPinnedStackAnimationEnded() throws RemoteException {
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6585793..449cca3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -7958,4 +7958,28 @@
throw re.rethrowFromSystemServer();
}
}
+
+
+ /**
+ * Called by the system to get a list of CA certificates that were installed by the device or
+ * profile owner.
+ *
+ * <p> The caller must be the target user's Device Owner/Profile owner or hold the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ *
+ * @param user The user for whom to retrieve information.
+ * @return list of aliases identifying CA certificates installed by the device or profile owner
+ * @throws SecurityException if the caller does not have permission to retrieve information
+ * about the given user's CA certificates.
+ *
+ * @hide
+ */
+ @TestApi
+ public List<String> getOwnerInstalledCaCerts(@NonNull UserHandle user) {
+ try {
+ return mService.getOwnerInstalledCaCerts(user).getList();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ec97c2c..97a4678 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -27,6 +27,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
+import android.content.pm.StringParceledListSlice;
import android.graphics.Bitmap;
import android.net.ProxyInfo;
import android.net.Uri;
@@ -349,4 +350,5 @@
boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags);
boolean isDefaultInputMethodSetByOwner(in UserHandle user);
+ StringParceledListSlice getOwnerInstalledCaCerts(in UserHandle user);
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index d1b2a15..db164e5 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -539,6 +539,7 @@
// fields (viewId and childId) of the field.
AutoFillId mAutoFillId;
AutoFillType mAutoFillType;
+ @View.AutoFillHint int mAutoFillHint;
AutoFillValue mAutoFillValue;
String[] mAutoFillOptions;
boolean mSanitized;
@@ -623,6 +624,7 @@
mSanitized = in.readInt() == 1;
mAutoFillId = in.readParcelable(null);
mAutoFillType = in.readParcelable(null);
+ mAutoFillHint = in.readInt();
mAutoFillValue = in.readParcelable(null);
mAutoFillOptions = in.readStringArray();
}
@@ -756,6 +758,7 @@
out.writeInt(mSanitized ? 1 : 0);
out.writeParcelable(mAutoFillId, 0);
out.writeParcelable(mAutoFillType, 0);
+ out.writeInt(mAutoFillHint);
final AutoFillValue sanitizedValue = writeSensitive ? mAutoFillValue : null;
out.writeParcelable(sanitizedValue, 0);
out.writeStringArray(mAutoFillOptions);
@@ -859,6 +862,20 @@
}
/**
+ * Describes the content of a view so that a auto-fill service can fill in the appropriate
+ * data.
+ *
+ * <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
+ * for assist.</p>
+ *
+ * @return The hint for this view
+ */
+ // TODO(b/35364993): add CTS/unit test
+ @View.AutoFillHint public int getAutoFillHint() {
+ return mAutoFillHint;
+ }
+
+ /**
* Gets the the value of this view.
*
* <p>It's only set when the {@link AssistStructure} is used for auto-filling purposes, not
@@ -1549,6 +1566,11 @@
}
@Override
+ public void setAutoFillHint(@View.AutoFillHint int hint) {
+ mNode.mAutoFillHint = hint;
+ }
+
+ @Override
public void setAutoFillValue(AutoFillValue value) {
mNode.mAutoFillValue = value;
}
@@ -1563,9 +1585,6 @@
mNode.mInputType = inputType;
}
- /**
- * @hide
- */
@Override
public void setSanitized(boolean sanitized) {
mNode.mSanitized = sanitized;
@@ -1695,6 +1714,7 @@
+ ", type=" + node.getAutoFillType()
+ ", options=" + Arrays.toString(node.getAutoFillOptions())
+ ", inputType=" + node.getInputType()
+ + ", hint=" + Integer.toHexString(node.getAutoFillHint())
+ ", value=" + node.getAutoFillValue()
+ ", sanitized=" + node.isSanitized());
}
diff --git a/core/java/android/app/usage/CacheQuotaHint.java b/core/java/android/app/usage/CacheQuotaHint.java
index 4b6f99b..1d5c2b0 100644
--- a/core/java/android/app/usage/CacheQuotaHint.java
+++ b/core/java/android/app/usage/CacheQuotaHint.java
@@ -24,8 +24,10 @@
import com.android.internal.util.Preconditions;
+import java.util.Objects;
+
/**
- * CacheQuotaRequest represents a triplet of a uid, the volume UUID it is stored upon, and
+ * CacheQuotaHint represents a triplet of a uid, the volume UUID it is stored upon, and
* its usage stats. When processed, it obtains a cache quota as defined by the system which
* allows apps to understand how much cache to use.
* {@hide}
@@ -78,6 +80,23 @@
return 0;
}
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof CacheQuotaHint) {
+ final CacheQuotaHint other = (CacheQuotaHint) o;
+ return Objects.equals(mUuid, other.mUuid)
+ && Objects.equals(mUsageStats, other.mUsageStats)
+ && mUid == other.mUid && mQuota == other.mQuota;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.mUuid, this.mUid, this.mUsageStats, this.mQuota);
+ }
+
public static final class Builder {
private String mUuid;
private int mUid;
@@ -100,7 +119,7 @@
}
public @NonNull Builder setUid(int uid) {
- Preconditions.checkArgumentPositive(uid, "Proposed uid was not positive.");
+ Preconditions.checkArgumentNonnegative(uid, "Proposed uid was negative.");
mUid = uid;
return this;
}
@@ -117,7 +136,6 @@
}
public @NonNull CacheQuotaHint build() {
- Preconditions.checkNotNull(mUsageStats);
return new CacheQuotaHint(this);
}
}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 077331e..67c791d 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -417,6 +417,17 @@
public static final String EXTRA_APPWIDGET_OLD_IDS = "appWidgetOldIds";
/**
+ * An extra that can be passed to
+ * {@link #requestPinAppWidget(ComponentName, Bundle, PendingIntent)}. This would allow the
+ * launcher app to present a custom preview to the user.
+ *
+ * <p>
+ * The value should be a {@link RemoteViews} similar to what is used with
+ * {@link #updateAppWidget} calls.
+ */
+ public static final String EXTRA_APPWIDGET_PREVIEW = "appWidgetPreview";
+
+ /**
* Field for the manifest meta-data tag.
*
* @see AppWidgetProviderInfo
@@ -1073,7 +1084,7 @@
/**
* Return {@code TRUE} if the default launcher supports
- * {@link #requestPinAppWidget(ComponentName, PendingIntent)}
+ * {@link #requestPinAppWidget(ComponentName, Bundle, PendingIntent)}
*/
public boolean isRequestPinAppWidgetSupported() {
try {
@@ -1084,6 +1095,15 @@
}
/**
+ * Only used during development. Can be deleted before release.
+ * @hide
+ */
+ public boolean requestPinAppWidget(@NonNull ComponentName provider,
+ @Nullable PendingIntent successCallback) {
+ return requestPinAppWidget(provider, null, successCallback);
+ }
+
+ /**
* Request to pin an app widget on the current launcher. It's up to the launcher to accept this
* request (optionally showing a user confirmation). If the request is accepted, the caller will
* get a confirmation with extra {@link #EXTRA_APPWIDGET_ID}.
@@ -1099,6 +1119,8 @@
*
* @param provider The {@link ComponentName} for the {@link
* android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
+ * @param extras In not null, this is passed to the launcher app. For eg {@link
+ * #EXTRA_APPWIDGET_PREVIEW} can be used for a custom preview.
* @param successCallback If not null, this intent will be sent when the widget is created.
*
* @return {@code TRUE} if the launcher supports this feature. Note the API will return without
@@ -1113,10 +1135,10 @@
* service or when the user is locked.
*/
public boolean requestPinAppWidget(@NonNull ComponentName provider,
- @Nullable PendingIntent successCallback) {
+ @Nullable Bundle extras, @Nullable PendingIntent successCallback) {
try {
- return mService.requestPinAppWidget(mPackageName, provider,
- successCallback == null ? null : successCallback.getIntentSender());
+ return mService.requestPinAppWidget(mPackageName, provider, extras,
+ successCallback == null ? null : successCallback.getIntentSender());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 4252482..7d8459c 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -50,6 +50,12 @@
public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
"android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
+ /* Extras used in ACTION_MESSAGE_RECEIVED intent */
+ public static final String EXTRA_SENDER_CONTACT_URI =
+ "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
+ public static final String EXTRA_SENDER_CONTACT_NAME =
+ "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
+
private IBluetoothMapClient mService;
private final Context mContext;
private ServiceListener mServiceListener;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 870db217..e0dc10d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1324,6 +1324,7 @@
* Output: nothing.
* @hide
*/
+ @SystemApi
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
diff --git a/core/java/android/content/pm/IPinItemRequest.aidl b/core/java/android/content/pm/IPinItemRequest.aidl
index eddce58..4b4dffa 100644
--- a/core/java/android/content/pm/IPinItemRequest.aidl
+++ b/core/java/android/content/pm/IPinItemRequest.aidl
@@ -27,4 +27,5 @@
boolean accept(in Bundle options);
ShortcutInfo getShortcutInfo();
AppWidgetProviderInfo getAppWidgetProviderInfo();
+ Bundle getExtras();
}
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index c3fd089..abdef08 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -1365,6 +1365,18 @@
}
/**
+ * Any extras sent by the requesting app.
+ */
+ @Nullable
+ public Bundle getExtras() {
+ try {
+ return mInner.getExtras();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Return {@code TRUE} if a request is valid -- i.e. {@link #accept(Bundle)} has not been
* called yet.
*/
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 696fe81..3de19d1 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.LauncherApps.ShortcutQuery;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import java.util.List;
@@ -72,8 +73,8 @@
@NonNull String callingPackage);
public abstract boolean requestPinAppWidget(@NonNull String callingPackage,
- @NonNull AppWidgetProviderInfo appWidget, @Nullable IntentSender resultIntent,
- int userId);
+ @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
+ @Nullable IntentSender resultIntent, int userId);
public abstract boolean isRequestPinItemSupported(int callingUserId, int requestType);
}
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 71a0349..33b5903 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -119,6 +119,9 @@
* <p>Location should specify a document URI or a tree URI with document ID. If
* this URI identifies a non-directory, document navigator will attempt to use the parent
* of the document as the initial location.
+ *
+ * <p>The initial location is system specific if this extra is missing or document navigator
+ * failed to locate the desired initial location.
*/
public static final String EXTRA_INITIAL_URI = "android.provider.extra.INITIAL_URI";
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index c3d3f39..16d4666 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -94,6 +94,7 @@
}
}
};
+ private boolean mAcceptAllTouches;
public NotificationHeaderView(Context context) {
this(context, null);
@@ -374,6 +375,8 @@
case MotionEvent.ACTION_DOWN:
mTrackGesture = false;
if (isInside(x, y)) {
+ mDownX = x;
+ mDownY = y;
mTrackGesture = true;
return true;
}
@@ -396,11 +399,12 @@
}
private boolean isInside(float x, float y) {
+ if (mAcceptAllTouches) {
+ return true;
+ }
for (int i = 0; i < mTouchRects.size(); i++) {
Rect r = mTouchRects.get(i);
if (r.contains((int) x, (int) y)) {
- mDownX = x;
- mDownY = y;
return true;
}
}
@@ -433,4 +437,9 @@
}
return mTouchListener.isInside(x, y);
}
+
+ @RemotableViewMethod
+ public void setAcceptAllTouches(boolean acceptAllTouches) {
+ mAcceptAllTouches = acceptAllTouches;
+ }
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b718696..519c1e2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -95,6 +95,11 @@
IBinder displayToken, int mode);
private static native void nativeDeferTransactionUntil(long nativeObject,
IBinder handle, long frame);
+ private static native void nativeDeferTransactionUntilSurface(long nativeObject,
+ long surfaceObject, long frame);
+ private static native void nativeReparentChildren(long nativeObject,
+ IBinder handle);
+ private static native void nativeSeverChildren(long nativeObject);
private static native void nativeSetOverrideScalingMode(long nativeObject,
int scalingMode);
private static native IBinder nativeGetHandle(long nativeObject);
@@ -418,7 +423,23 @@
}
public void deferTransactionUntil(IBinder handle, long frame) {
- nativeDeferTransactionUntil(mNativeObject, handle, frame);
+ if (frame > 0) {
+ nativeDeferTransactionUntil(mNativeObject, handle, frame);
+ }
+ }
+
+ public void deferTransactionUntil(Surface barrier, long frame) {
+ if (frame > 0) {
+ nativeDeferTransactionUntilSurface(mNativeObject, barrier.mNativeObject, frame);
+ }
+ }
+
+ public void reparentChildren(IBinder newParentHandle) {
+ nativeReparentChildren(mNativeObject, newParentHandle);
+ }
+
+ public void detachChildren() {
+ nativeSeverChildren(mNativeObject);
}
public void setOverrideScalingMode(int scalingMode) {
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index 3cf5af4..b5912bc 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -27,6 +27,7 @@
private long mNativeClient; // SurfaceComposerClient*
private static native long nativeCreate();
+ private static native long nativeCreateScoped(long surfacePtr);
private static native void nativeDestroy(long ptr);
private static native void nativeKill(long ptr);
@@ -35,6 +36,10 @@
mNativeClient = nativeCreate();
}
+ public SurfaceSession(Surface root) {
+ mNativeClient = nativeCreateScoped(root.mNativeObject);
+ }
+
/* no user serviceable parts here ... */
@Override
protected void finalize() throws Throwable {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index d2577d4..61b1247 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,6 +16,10 @@
package android.view;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER;
+
import android.content.Context;
import android.content.res.CompatibilityInfo.Translator;
import android.content.res.Configuration;
@@ -26,16 +30,12 @@
import android.graphics.Region;
import android.os.Handler;
import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
-import com.android.internal.view.BaseIWindow;
import com.android.internal.view.SurfaceCallbackHelper;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
@@ -92,8 +92,8 @@
* positioned asynchronously.</p>
*/
public class SurfaceView extends View {
- static private final String TAG = "SurfaceView";
- static private final boolean DEBUG = false;
+ private static final String TAG = "SurfaceView";
+ private static final boolean DEBUG = false;
final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();
@@ -102,28 +102,23 @@
final ReentrantLock mSurfaceLock = new ReentrantLock();
final Surface mSurface = new Surface(); // Current surface in use
- final Surface mNewSurface = new Surface(); // New surface we are switching to
boolean mDrawingStopped = true;
+ // We use this to track if the application has produced a frame
+ // in to the Surface. Up until that point, we should be careful not to punch
+ // holes.
+ boolean mDrawFinished = false;
- final WindowManager.LayoutParams mLayout
- = new WindowManager.LayoutParams();
- IWindowSession mSession;
- MyWindow mWindow;
- final Rect mVisibleInsets = new Rect();
- final Rect mWinFrame = new Rect();
- final Rect mOverscanInsets = new Rect();
- final Rect mContentInsets = new Rect();
- final Rect mStableInsets = new Rect();
- final Rect mOutsets = new Rect();
- final Rect mBackdropFrame = new Rect();
+ final Rect mScreenRect = new Rect();
+ SurfaceSession mSurfaceSession;
+
+ SurfaceControl mSurfaceControl;
final Rect mTmpRect = new Rect();
final Configuration mConfiguration = new Configuration();
static final int KEEP_SCREEN_ON_MSG = 1;
- static final int GET_NEW_SURFACE_MSG = 2;
- static final int UPDATE_WINDOW_MSG = 3;
+ static final int DRAW_FINISHED_MSG = 2;
- int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
boolean mIsCreating = false;
private volatile boolean mRtHandlingPositionUpdates = false;
@@ -135,11 +130,9 @@
case KEEP_SCREEN_ON_MSG: {
setKeepScreenOn(msg.arg1 != 0);
} break;
- case GET_NEW_SURFACE_MSG: {
- handleGetNewSurface();
- } break;
- case UPDATE_WINDOW_MSG: {
- updateWindow();
+ case DRAW_FINISHED_MSG: {
+ mDrawFinished = true;
+ invalidate();
} break;
}
}
@@ -149,7 +142,7 @@
= new ViewTreeObserver.OnScrollChangedListener() {
@Override
public void onScrollChanged() {
- updateWindow();
+ updateSurface();
}
};
@@ -159,13 +152,14 @@
public boolean onPreDraw() {
// reposition ourselves where the surface is
mHaveFrame = getWidth() > 0 && getHeight() > 0;
- updateWindow();
+ updateSurface();
return true;
}
};
boolean mRequestedVisible = false;
boolean mWindowVisibility = false;
+ boolean mLastWindowVisibility = false;
boolean mViewVisibility = false;
int mRequestedWidth = -1;
int mRequestedHeight = -1;
@@ -181,19 +175,17 @@
boolean mVisible = false;
int mWindowSpaceLeft = -1;
int mWindowSpaceTop = -1;
- int mWindowSpaceWidth = -1;
- int mWindowSpaceHeight = -1;
+ int mSurfaceWidth = -1;
+ int mSurfaceHeight = -1;
int mFormat = -1;
final Rect mSurfaceFrame = new Rect();
int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
- boolean mUpdateWindowNeeded;
- boolean mReportDrawNeeded;
private Translator mTranslator;
- private int mWindowInsetLeft;
- private int mWindowInsetTop;
private boolean mGlobalListenersAdded;
+ private int mSurfaceFlags = SurfaceControl.HIDDEN;
+
public SurfaceView(Context context) {
this(context, null);
}
@@ -227,11 +219,8 @@
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mParent.requestTransparentRegion(this);
- mSession = getWindowSession();
- mLayout.token = getWindowToken();
- mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle());
- mLayout.packageName = mContext.getOpPackageName();
mViewVisibility = getVisibility() == VISIBLE;
+ mRequestedVisible = mViewVisibility && mWindowVisibility;
if (!mGlobalListenersAdded) {
ViewTreeObserver observer = getViewTreeObserver();
@@ -246,7 +235,7 @@
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
- updateWindow();
+ updateSurface();
}
@Override
@@ -264,7 +253,7 @@
requestLayout();
}
mRequestedVisible = newRequestedVisible;
- updateWindow();
+ updateSurface();
}
@Override
@@ -277,19 +266,14 @@
}
mRequestedVisible = false;
- updateWindow();
- mHaveFrame = false;
- if (mWindow != null) {
- try {
- mSession.remove(mWindow);
- } catch (RemoteException ex) {
- // Not much we can do here...
- }
- mWindow = null;
- }
- mSession = null;
- mLayout.token = null;
+ updateSurface();
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
+ }
+ mSurfaceControl = null;
+
+ mHaveFrame = false;
super.onDetachedFromWindow();
}
@@ -308,13 +292,13 @@
@Override
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean result = super.setFrame(left, top, right, bottom);
- updateWindow();
+ updateSurface();
return result;
}
@Override
public boolean gatherTransparentRegion(Region region) {
- if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+ if (isAboveParent()) {
return super.gatherTransparentRegion(region);
}
@@ -341,7 +325,7 @@
@Override
public void draw(Canvas canvas) {
- if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+ if (mDrawFinished && !isAboveParent()) {
// draw() is not called when SKIP_DRAW is set
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// punch a whole in the view-hierarchy below us
@@ -353,8 +337,8 @@
@Override
protected void dispatchDraw(Canvas canvas) {
- if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
- // if SKIP_DRAW is cleared, draw() has already punched a hole
+ if (mDrawFinished && !isAboveParent()) {
+ // draw() is not called when SKIP_DRAW is set
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -375,9 +359,8 @@
* <p>Calling this overrides any previous call to {@link #setZOrderOnTop}.
*/
public void setZOrderMediaOverlay(boolean isMediaOverlay) {
- mWindowType = isMediaOverlay
- ? WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
- : WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ mSubLayer = isMediaOverlay
+ ? APPLICATION_MEDIA_OVERLAY_SUBLAYER : APPLICATION_MEDIA_SUBLAYER;
}
/**
@@ -395,12 +378,9 @@
*/
public void setZOrderOnTop(boolean onTop) {
if (onTop) {
- mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
- // ensures the surface is placed below the IME
- mLayout.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ mSubLayer = APPLICATION_PANEL_SUBLAYER;
} else {
- mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
- mLayout.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ mSubLayer = APPLICATION_MEDIA_SUBLAYER;
}
}
@@ -418,31 +398,32 @@
*/
public void setSecure(boolean isSecure) {
if (isSecure) {
- mLayout.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+ mSurfaceFlags |= SurfaceControl.SECURE;
} else {
- mLayout.flags &= ~WindowManager.LayoutParams.FLAG_SECURE;
+ mSurfaceFlags &= ~SurfaceControl.SECURE;
}
}
- /**
- * Hack to allow special layering of windows. The type is one of the
- * types in WindowManager.LayoutParams. This is a hack so:
- * @hide
- */
- public void setWindowType(int type) {
- mWindowType = type;
+ private Rect getParentSurfaceInsets() {
+ final ViewRootImpl root = getViewRootImpl();
+ if (root == null) {
+ return null;
+ } else {
+ return root.mWindowAttributes.surfaceInsets;
+ }
}
/** @hide */
- protected void updateWindow() {
+ protected void updateSurface() {
if (!mHaveFrame) {
return;
}
ViewRootImpl viewRoot = getViewRootImpl();
- if (viewRoot != null) {
- mTranslator = viewRoot.mTranslator;
+ if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
+ return;
}
+ mTranslator = viewRoot.mTranslator;
if (mTranslator != null) {
mSurface.setCompatibilityTranslator(mTranslator);
}
@@ -452,17 +433,15 @@
int myHeight = mRequestedHeight;
if (myHeight <= 0) myHeight = getHeight();
- final boolean creating = mWindow == null;
final boolean formatChanged = mFormat != mRequestedFormat;
- final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight;
+ final boolean creating = (mSurfaceControl == null || formatChanged)
+ && mRequestedVisible;
+ final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible;
- final boolean layoutSizeChanged = getWidth() != mLayout.width
- || getHeight() != mLayout.height;
-
+ final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
boolean redrawNeeded = false;
- if (creating || formatChanged || sizeChanged || visibleChanged
- || mUpdateWindowNeeded || mReportDrawNeeded) {
+ if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
getLocationInWindow(mLocation);
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
@@ -476,93 +455,77 @@
final boolean visible = mVisible = mRequestedVisible;
mWindowSpaceLeft = mLocation[0];
mWindowSpaceTop = mLocation[1];
- mWindowSpaceWidth = myWidth;
- mWindowSpaceHeight = myHeight;
+ mSurfaceWidth = myWidth;
+ mSurfaceHeight = myHeight;
mFormat = mRequestedFormat;
+ mLastWindowVisibility = mWindowVisibility;
- // Scaling/Translate window's layout here because mLayout is not used elsewhere.
-
- // Places the window relative
- mLayout.x = mWindowSpaceLeft;
- mLayout.y = mWindowSpaceTop;
- mLayout.width = getWidth();
- mLayout.height = getHeight();
+ mScreenRect.left = mWindowSpaceLeft;
+ mScreenRect.top = mWindowSpaceTop;
+ mScreenRect.right = mWindowSpaceLeft + getWidth();
+ mScreenRect.bottom = mWindowSpaceTop + getHeight();
if (mTranslator != null) {
- mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout);
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
}
- mLayout.format = mRequestedFormat;
- mLayout.flags |=WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
- | WindowManager.LayoutParams.FLAG_SCALED
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- ;
- if (!creating && !sizeChanged) {
- mLayout.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY;
- } else {
- mLayout.privateFlags &=
- ~WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY;
+ final Rect surfaceInsets = getParentSurfaceInsets();
+ mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+
+ if (creating) {
+ mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
+ mSurfaceControl = new SurfaceControl(mSurfaceSession,
+ "SurfaceView - " + viewRoot.getTitle().toString(),
+ mSurfaceWidth, mSurfaceHeight, mFormat,
+ mSurfaceFlags);
}
- if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
- mLayout.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- }
- mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
- | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
-
- if (mWindow == null) {
- Display display = getDisplay();
- mWindow = new MyWindow(this);
- mLayout.type = mWindowType;
- mLayout.gravity = Gravity.START|Gravity.TOP;
- mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
- mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets,
- mStableInsets);
- }
-
- boolean realSizeChanged;
- boolean reportDrawNeeded;
-
- int relayoutResult;
+ boolean realSizeChanged = false;
mSurfaceLock.lock();
try {
- mUpdateWindowNeeded = false;
- reportDrawNeeded = mReportDrawNeeded;
- mReportDrawNeeded = false;
mDrawingStopped = !visible;
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Cur surface: " + mSurface);
- relayoutResult = mSession.relayout(
- mWindow, mWindow.mSeq, mLayout, mWindowSpaceWidth, mWindowSpaceHeight,
- visible ? VISIBLE : GONE,
- WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
- mWinFrame, mOverscanInsets, mContentInsets,
- mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
- mConfiguration, mNewSurface);
- if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
- reportDrawNeeded = true;
+ SurfaceControl.openTransaction();
+ try {
+ mSurfaceControl.setLayer(mSubLayer);
+ if (mViewVisibility) {
+ mSurfaceControl.show();
+ } else {
+ mSurfaceControl.hide();
+ }
+
+ // While creating the surface, we will set it's initial
+ // geometry. Outside of that though, we should generally
+ // leave it to the RenderThread.
+ if (creating || !mRtHandlingPositionUpdates) {
+ mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
+ mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ mScreenRect.height() / (float) mSurfaceHeight);
+ }
+ if (sizeChanged) {
+ mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
+ }
+ } finally {
+ SurfaceControl.closeTransaction();
}
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "New surface: " + mNewSurface
- + ", vis=" + visible + ", frame=" + mWinFrame);
+ if (sizeChanged || creating) {
+ redrawNeeded = true;
+ }
mSurfaceFrame.left = 0;
mSurfaceFrame.top = 0;
if (mTranslator == null) {
- mSurfaceFrame.right = mWinFrame.width();
- mSurfaceFrame.bottom = mWinFrame.height();
+ mSurfaceFrame.right = mSurfaceWidth;
+ mSurfaceFrame.bottom = mSurfaceHeight;
} else {
float appInvertedScale = mTranslator.applicationInvertedScale;
- mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
- mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
+ mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
+ mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
}
final int surfaceWidth = mSurfaceFrame.right;
@@ -576,12 +539,11 @@
}
try {
- redrawNeeded |= creating | reportDrawNeeded;
+ redrawNeeded |= visible && !mDrawFinished;
SurfaceHolder.Callback callbacks[] = null;
- final boolean surfaceChanged = (relayoutResult
- & WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0;
+ final boolean surfaceChanged = creating;
if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
mSurfaceCreated = false;
if (mSurface.isValid()) {
@@ -608,7 +570,10 @@
}
}
- mSurface.transferFrom(mNewSurface);
+ if (creating) {
+ mSurface.copyFrom(mSurfaceControl);
+ }
+
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
@@ -641,53 +606,55 @@
callbacks = getSurfaceCallbacks();
}
SurfaceCallbackHelper sch =
- new SurfaceCallbackHelper(mSession, mWindow);
+ new SurfaceCallbackHelper(this::onDrawFinished);
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
}
}
} finally {
mIsCreating = false;
- mSession.performDeferredDestroy(mWindow);
+ if (mSurfaceControl != null && !mSurfaceCreated) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
}
- } catch (RemoteException ex) {
+ } catch (Exception ex) {
Log.e(TAG, "Exception from relayout", ex);
}
if (DEBUG) Log.v(
- TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
- " w=" + mLayout.width + " h=" + mLayout.height +
- ", frame=" + mSurfaceFrame);
+ TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
+ + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
+ + ", frame=" + mSurfaceFrame);
} else {
// Calculate the window position in case RT loses the window
// and we need to fallback to a UI-thread driven position update
- getLocationInWindow(mLocation);
+ getLocationInSurface(mLocation);
final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
|| mWindowSpaceTop != mLocation[1];
+ final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
+ || getHeight() != mScreenRect.height();
if (positionChanged || layoutSizeChanged) { // Only the position has changed
mWindowSpaceLeft = mLocation[0];
mWindowSpaceTop = mLocation[1];
- // For our size changed check, we keep mLayout.width and mLayout.height
+ // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
// in view local space.
- mLocation[0] = mLayout.width = getWidth();
- mLocation[1] = mLayout.height = getHeight();
+ mLocation[0] = getWidth();
+ mLocation[1] = getHeight();
- transformFromViewToWindowSpace(mLocation);
-
- mTmpRect.set(mWindowSpaceLeft, mWindowSpaceTop,
+ mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
mLocation[0], mLocation[1]);
if (mTranslator != null) {
- mTranslator.translateRectInAppWindowToScreen(mTmpRect);
+ mTranslator.translateRectInAppWindowToScreen(mScreenRect);
}
if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
try {
- if (DEBUG) Log.d(TAG, String.format("%d updateWindowPosition UI, " +
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- mTmpRect.left, mTmpRect.top,
- mTmpRect.right, mTmpRect.bottom));
- mSession.repositionChild(mWindow, mTmpRect.left, mTmpRect.top,
- mTmpRect.right, mTmpRect.bottom, -1, mTmpRect);
- } catch (RemoteException ex) {
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, -1);
+ } catch (Exception ex) {
Log.e(TAG, "Exception from relayout", ex);
}
}
@@ -695,20 +662,43 @@
}
}
+ private void onDrawFinished() {
+ if (DEBUG) {
+ Log.i(TAG, System.identityHashCode(this) + " "
+ + "finishedDrawing");
+ }
+ mHandler.sendEmptyMessage(DRAW_FINISHED_MSG);
+ }
+
+ private void setParentSpaceRectangle(Rect position, long frameNumber) {
+ ViewRootImpl viewRoot = getViewRootImpl();
+
+ SurfaceControl.openTransaction();
+ try {
+ if (frameNumber > 0) {
+ mSurfaceControl.deferTransactionUntil(viewRoot.mSurface, frameNumber);
+ }
+ mSurfaceControl.setPosition(position.left, position.top);
+ mSurfaceControl.setMatrix(position.width() / (float) mSurfaceWidth,
+ 0.0f, 0.0f,
+ position.height() / (float) mSurfaceHeight);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+
private Rect mRTLastReportedPosition = new Rect();
/**
* Called by native by a Rendering Worker thread to update the window position
* @hide
*/
- public final void updateWindowPosition_renderWorker(long frameNumber,
+ public final void updateSurfacePosition_renderWorker(long frameNumber,
int left, int top, int right, int bottom) {
- IWindowSession session = mSession;
- MyWindow window = mWindow;
- if (session == null || window == null) {
- // Guess we got detached, that sucks
+ if (mSurfaceControl == null) {
return;
}
+
// TODO: This is teensy bit racey in that a brand new SurfaceView moving on
// its 2nd frame if RenderThread is running slowly could potentially see
// this as false, enter the branch, get pre-empted, then this comes along
@@ -726,35 +716,29 @@
}
try {
if (DEBUG) {
- Log.d(TAG, String.format("%d updateWindowPosition RenderWorker, frameNr = %d, " +
+ Log.d(TAG, String.format("%d updateSurfacePosition RenderWorker, frameNr = %d, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
frameNumber, left, top, right, bottom));
}
- // Just using mRTLastReportedPosition as a dummy rect here
- session.repositionChild(window, left, top, right, bottom,
- frameNumber,
- mRTLastReportedPosition);
- // Now overwrite mRTLastReportedPosition with our values
mRTLastReportedPosition.set(left, top, right, bottom);
- } catch (RemoteException ex) {
+ setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+ // Now overwrite mRTLastReportedPosition with our values
+ } catch (Exception ex) {
Log.e(TAG, "Exception from repositionChild", ex);
}
}
/**
- * Called by native on RenderThread to notify that the window is no longer in the
+ * Called by native on RenderThread to notify that the view is no longer in the
* draw tree. UI thread is blocked at this point.
* @hide
*/
- public final void windowPositionLost_uiRtSync(long frameNumber) {
+ public final void surfacePositionLost_uiRtSync(long frameNumber) {
if (DEBUG) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
}
- IWindowSession session = mSession;
- MyWindow window = mWindow;
- if (session == null || window == null) {
- // We got detached prior to receiving this, abort
+ if (mSurfaceControl == null) {
return;
}
if (mRtHandlingPositionUpdates) {
@@ -763,19 +747,14 @@
// safely access other member variables at this time.
// So do what the UI thread would have done if RT wasn't handling position
// updates.
- mTmpRect.set(mLayout.x, mLayout.y,
- mLayout.x + mLayout.width,
- mLayout.y + mLayout.height);
-
- if (!mTmpRect.isEmpty() && !mTmpRect.equals(mRTLastReportedPosition)) {
+ if (!mScreenRect.isEmpty() && !mScreenRect.equals(mRTLastReportedPosition)) {
try {
- if (DEBUG) Log.d(TAG, String.format("%d updateWindowPosition, " +
+ if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition, " +
"postion = [%d, %d, %d, %d]", System.identityHashCode(this),
- mTmpRect.left, mTmpRect.top,
- mTmpRect.right, mTmpRect.bottom));
- session.repositionChild(window, mTmpRect.left, mTmpRect.top,
- mTmpRect.right, mTmpRect.bottom, frameNumber, mWinFrame);
- } catch (RemoteException ex) {
+ mScreenRect.left, mScreenRect.top,
+ mScreenRect.right, mScreenRect.bottom));
+ setParentSpaceRectangle(mScreenRect, frameNumber);
+ } catch (Exception ex) {
Log.e(TAG, "Exception from relayout", ex);
}
}
@@ -792,10 +771,6 @@
return callbacks;
}
- void handleGetNewSurface() {
- updateWindow();
- }
-
/**
* Check to see if the surface has fixed size dimensions or if the surface's
* dimensions are dimensions are dependent on its current layout.
@@ -807,65 +782,8 @@
return (mRequestedWidth != -1 || mRequestedHeight != -1);
}
- private static class MyWindow extends BaseIWindow {
- private final WeakReference<SurfaceView> mSurfaceView;
-
- public MyWindow(SurfaceView surfaceView) {
- mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
- }
-
- @Override
- public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
- Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
- Configuration newConfig, Rect backDropRect, boolean forceLayout,
- boolean alwaysConsumeNavBar, int displayId) {
- SurfaceView surfaceView = mSurfaceView.get();
- if (surfaceView != null) {
- if (DEBUG) Log.v(TAG, surfaceView + " got resized: w=" + frame.width()
- + " h=" + frame.height() + ", cur w=" + mCurWidth + " h=" + mCurHeight);
- surfaceView.mSurfaceLock.lock();
- try {
- if (reportDraw) {
- surfaceView.mUpdateWindowNeeded = true;
- surfaceView.mReportDrawNeeded = true;
- surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
- } else if (surfaceView.mWinFrame.width() != frame.width()
- || surfaceView.mWinFrame.height() != frame.height()
- || forceLayout) {
- surfaceView.mUpdateWindowNeeded = true;
- surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
- }
- } finally {
- surfaceView.mSurfaceLock.unlock();
- }
- }
- }
-
- @Override
- public void dispatchAppVisibility(boolean visible) {
- // The point of SurfaceView is to let the app control the surface.
- }
-
- @Override
- public void dispatchGetNewSurface() {
- SurfaceView surfaceView = mSurfaceView.get();
- if (surfaceView != null) {
- Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG);
- surfaceView.mHandler.sendMessage(msg);
- }
- }
-
- @Override
- public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
- Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled);
- }
-
- @Override
- public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
- }
-
- int mCurWidth = -1;
- int mCurHeight = -1;
+ private boolean isAboveParent() {
+ return mSubLayer >= 0;
}
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
@@ -913,15 +831,14 @@
@Override
public void setFormat(int format) {
-
// for backward compatibility reason, OPAQUE always
// means 565 for SurfaceView
if (format == PixelFormat.OPAQUE)
format = PixelFormat.RGB_565;
mRequestedFormat = format;
- if (mWindow != null) {
- updateWindow();
+ if (mSurfaceControl != null) {
+ updateSurface();
}
}
@@ -982,10 +899,10 @@
mSurfaceLock.lock();
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
- + mDrawingStopped + ", win=" + mWindow);
+ + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);
Canvas c = null;
- if (!mDrawingStopped && mWindow != null) {
+ if (!mDrawingStopped && mSurfaceControl != null) {
try {
if (hardware) {
c = mSurface.lockHardwareCanvas();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e349170..99051a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -974,6 +974,143 @@
*/
public static final int AUTO_FILL_MODE_MANUAL = 2;
+ /** @hide */
+ @IntDef({
+ AUTO_FILL_HINT_NONE,
+ AUTO_FILL_HINT_EMAIL_ADDRESS,
+ AUTO_FILL_HINT_NAME,
+ AUTO_FILL_HINT_POSTAL_ADDRESS,
+ AUTO_FILL_HINT_PASSWORD,
+ AUTO_FILL_HINT_PHONE,
+ AUTO_FILL_HINT_USERNAME,
+ AUTO_FILL_HINT_POSTAL_CODE,
+ AUTO_FILL_HINT_CREDIT_CARD_NUMBER,
+ AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE,
+ AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE,
+ AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH,
+ AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR,
+ AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AutoFillHint {}
+
+ /**
+ * No auto-fill hint is set.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_NONE = 0;
+
+ /**
+ * This view contains an email address.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_EMAIL_ADDRESS = 0x1;
+
+ /**
+ * The view contains a real name.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_NAME = 0x2;
+
+ /**
+ * The view contains a user name.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_USERNAME = 0x4;
+
+ /**
+ * The view contains a password.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_PASSWORD = 0x8;
+
+ /**
+ * The view contains a phone number.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_PHONE = 0x10;
+
+ /**
+ * The view contains a postal address.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_POSTAL_ADDRESS = 0x20;
+
+ /**
+ * The view contains a postal code.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_POSTAL_CODE = 0x40;
+
+ /**
+ * The view contains a credit card number.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_NUMBER = 0x80;
+
+ /**
+ * The view contains a credit card security code.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_SECURITY_CODE = 0x100;
+
+ /**
+ * The view contains a credit card expiration date.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 0x200;
+
+ /**
+ * The view contains the month a credit card expires.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 0x400;
+
+ /**
+ * The view contains the year a credit card expires.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 0x800;
+
+ /**
+ * The view contains the day a credit card expires.
+ *
+ * Use with {@link #setAutoFillHint(int)} and <a href="#attr_android:autoFillHint">
+ * {@code android:autoFillHint}.
+ */
+ public static final int AUTO_FILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 0x1000;
+
+ /**
+ * Hint for the auto-fill services that describes the content of the view.
+ */
+ @AutoFillHint private int mAutoFillHint;
+
/**
* This view is enabled. Interpretation varies by subclass.
* Use with ENABLED_MASK when calling setFlags.
@@ -4799,11 +4936,16 @@
setFocusedByDefault(a.getBoolean(attr, true));
}
break;
- case com.android.internal.R.styleable.View_autoFillMode:
+ case R.styleable.View_autoFillMode:
if (a.peekValue(attr) != null) {
setAutoFillMode(a.getInt(attr, AUTO_FILL_MODE_INHERIT));
}
break;
+ case R.styleable.View_autoFillHint:
+ if (a.peekValue(attr) != null) {
+ setAutoFillHint(a.getInt(attr, AUTO_FILL_HINT_NONE));
+ }
+ break;
}
}
@@ -7035,6 +7177,7 @@
// to reuse the accessibility id to save space.
structure.setAutoFillId(getAccessibilityViewId());
structure.setAutoFillType(autoFillType);
+ structure.setAutoFillHint(getAutoFillHint());
structure.setAutoFillValue(getAutoFillValue());
}
}
@@ -7178,7 +7321,7 @@
/**
* Describes the auto-fill type that should be used on calls to
* {@link #autoFill(AutoFillValue)} and {@link #autoFillVirtual(int, AutoFillValue)}.
-
+ *
* <p>By default returns {@code null}, but views should override it (and
* {@link #autoFill(AutoFillValue)} to support the AutoFill Framework.
*/
@@ -7188,9 +7331,21 @@
}
/**
+ * Describes the content of a view so that a auto-fill service can fill in the appropriate data.
+ *
+ * @return The hint set via the attribute
+ *
+ * @attr ref android.R.styleable#View_autoFillHint
+ */
+ @ViewDebug.ExportedProperty()
+ @AutoFillHint public int getAutoFillHint() {
+ return mAutoFillHint;
+ }
+
+ /**
* Gets the {@link View}'s current auto-fill value.
*
- * <p>By default returns {@code null}, but views should override it,
+ * <p>By default returns {@code null}, but views should override it (and
* {@link #autoFill(AutoFillValue)}, and {@link #getAutoFillType()} to support the AutoFill
* Framework.
*/
@@ -8698,6 +8853,17 @@
}
/**
+ * Sets the a hint that helps the auto-fill service to select the appropriate data to fill the
+ * view.
+ *
+ * @param autoFillHint The auto-fill hint to set
+ * @attr ref android.R.styleable#View_autoFillHint
+ */
+ public void setAutoFillHint(@AutoFillHint int autoFillHint) {
+ mAutoFillHint = autoFillHint;
+ }
+
+ /**
* Set whether this view should have sound effects enabled for events such as
* clicking and touching.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 20d960f..f9863b0 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2632,6 +2632,14 @@
}
}
+ private void onDrawFinished() {
+ try {
+ mWindowSession.finishDrawing(mWindow);
+ } catch (RemoteException e) {
+ // Have fun!
+ }
+ }
+
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
@@ -2682,7 +2690,7 @@
}
if (mSurfaceHolder != null && mSurface.isValid()) {
- SurfaceCallbackHelper sch = new SurfaceCallbackHelper(mWindowSession, mWindow);
+ SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::onDrawFinished);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 43560f0..bc0960c 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -301,6 +301,13 @@
public abstract void setAutoFillType(AutoFillType info);
/**
+ * Sets the a hint that helps the auto-fill service to select the appropriate data to fill the
+ * view.
+ */
+ // TODO(b/35364993): add CTS/unit test
+ public abstract void setAutoFillHint(@View.AutoFillHint int hint);
+
+ /**
* Sets the {@link AutoFillValue} representing the current value of this node.
*/
// TODO(b/33197203, b/33802548): add CTS/unit test
diff --git a/core/java/android/view/autofill/AutoFillType.java b/core/java/android/view/autofill/AutoFillType.java
index e974705..536d5e0 100644
--- a/core/java/android/view/autofill/AutoFillType.java
+++ b/core/java/android/view/autofill/AutoFillType.java
@@ -21,16 +21,11 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
-import android.widget.TextView;
/**
* Defines the type of a object that can be used to auto-fill a {@link View} so the
* {@link android.service.autofill.AutoFillService} can use the proper {@link AutoFillValue} to
* fill it.
- *
- * <p>Some {@link AutoFillType}s can have an optional {@code sub-type}: the
- * main {@code type} defines the view's UI control category (like a text field), while the optional
- * {@code sub-type} define its semantics (like a postal address).
*/
public final class AutoFillType implements Parcelable {
@@ -38,9 +33,10 @@
// class idiom" (Effective Java, Item 71) to avoid memory utilization when auto-fill is not
// enabled.
private static class DefaultTypesHolder {
- static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE, 0);
- static final AutoFillType LIST = new AutoFillType(TYPE_LIST, 0);
- static final AutoFillType DATE = new AutoFillType(TYPE_DATE, 0);
+ static final AutoFillType TEXT = new AutoFillType(TYPE_TEXT);
+ static final AutoFillType TOGGLE = new AutoFillType(TYPE_TOGGLE);
+ static final AutoFillType LIST = new AutoFillType(TYPE_LIST);
+ static final AutoFillType DATE = new AutoFillType(TYPE_DATE);
}
private static final int TYPE_TEXT = 1;
@@ -49,11 +45,9 @@
private static final int TYPE_DATE = 4;
private final int mType;
- private final int mSubType;
- private AutoFillType(int type, int subType) {
+ private AutoFillType(int type) {
mType = type;
- mSubType = subType;
}
/**
@@ -62,8 +56,6 @@
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
* {@link AutoFillValue#forText(CharSequence)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getTextValue()}.
- *
- * <p>Sub-type for this type is the value defined by {@link TextView#getInputType()}.
*/
public boolean isText() {
return mType == TYPE_TEXT;
@@ -75,8 +67,6 @@
* <p>{@link AutoFillValue} instances for auto-filling a {@link View} can be obtained through
* {@link AutoFillValue#forToggle(boolean)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getToggleValue()}.
- *
- * <p>This type has no sub-types.
*/
public boolean isToggle() {
return mType == TYPE_TOGGLE;
@@ -90,7 +80,8 @@
* {@link AutoFillValue#forList(int)}, and the value passed to auto-fill a
* {@link View} can be fetched through {@link AutoFillValue#getListValue()}.
*
- * <p>This type has no sub-types.
+ * <p>The available options in the selection list are typically provided by
+ * {@link android.app.assist.AssistStructure.ViewNode#getAutoFillOptions()}.
*/
public boolean isList() {
return mType == TYPE_LIST;
@@ -111,15 +102,6 @@
return mType == TYPE_DATE;
}
- /**
- * Gets the optional sub-type, representing the {@link View}'s semantic.
- *
- * @return {@code 0} if type does not support sub-types.
- */
- public int getSubType() {
- return mSubType;
- }
-
/////////////////////////////////////
// Object "contract" methods. //
/////////////////////////////////////
@@ -128,14 +110,13 @@
public String toString() {
if (!DEBUG) return super.toString();
- return "AutoFillType [type=" + mType + ", subType=" + mSubType + "]";
+ return "AutoFillType [type=" + mType + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + mSubType;
result = prime * result + mType;
return result;
}
@@ -146,7 +127,6 @@
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final AutoFillType other = (AutoFillType) obj;
- if (mSubType != other.mSubType) return false;
if (mType != other.mType) return false;
return true;
}
@@ -163,12 +143,10 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mType);
- parcel.writeInt(mSubType);
}
private AutoFillType(Parcel parcel) {
mType = parcel.readInt();
- mSubType = parcel.readInt();
}
public static final Parcelable.Creator<AutoFillType> CREATOR =
@@ -193,8 +171,8 @@
*
* <p>See {@link #isText()} for more info.
*/
- public static AutoFillType forText(int inputType) {
- return new AutoFillType(TYPE_TEXT, inputType);
+ public static AutoFillType forText() {
+ return DefaultTypesHolder.TEXT;
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3d10917..97fc835 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10019,7 +10019,7 @@
@Override
@Nullable
public AutoFillType getAutoFillType() {
- return isTextEditable() ? AutoFillType.forText(getInputType()) : null;
+ return isTextEditable() ? AutoFillType.forText() : null;
}
@Override
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 79301aa..c235ebd 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1035,7 +1035,8 @@
LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
query.setIntent(getTargetIntent());
query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_CHOOSER);
- List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, UserHandle.SYSTEM);
+ List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query,
+ android.os.Process.myUserHandle());
if (DEBUG) Log.d(TAG, "Adding " + shortcuts.size() + " chooser shortcuts");
addShortcuts(shortcuts);
mAreChooserShortcutsRetrieved = true;
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 81db93d..f987a9f 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -68,7 +68,7 @@
int[] getAppWidgetIds(in ComponentName providerComponent);
boolean isBoundWidgetPackage(String packageName, int userId);
boolean requestPinAppWidget(String packageName, in ComponentName providerComponent,
- in IntentSender resultIntent);
+ in Bundle extras, in IntentSender resultIntent);
boolean isRequestPinAppWidgetSupported();
}
diff --git a/core/java/com/android/internal/view/SurfaceCallbackHelper.java b/core/java/com/android/internal/view/SurfaceCallbackHelper.java
index 5b6a82c..507b673 100644
--- a/core/java/com/android/internal/view/SurfaceCallbackHelper.java
+++ b/core/java/com/android/internal/view/SurfaceCallbackHelper.java
@@ -17,14 +17,11 @@
package com.android.internal.view;
import android.os.RemoteException;
-import android.view.IWindow;
-import android.view.IWindowSession;
import android.view.Surface;
import android.view.SurfaceHolder;
public class SurfaceCallbackHelper {
- IWindowSession mSession;
- IWindow.Stub mWindow;
+ Runnable mRunnable;
int mFinishDrawingCollected = 0;
int mFinishDrawingExpected = 0;
@@ -37,26 +34,18 @@
if (mFinishDrawingCollected < mFinishDrawingExpected) {
return;
}
- try {
- mSession.finishDrawing(mWindow);
- } catch (RemoteException e) {
- }
+ mRunnable.run();
}
}
};
- public SurfaceCallbackHelper(IWindowSession session,
- IWindow.Stub window) {
- mSession = session;
- mWindow = window;
+ public SurfaceCallbackHelper(Runnable callbacksCollected) {
+ mRunnable = callbacksCollected;
}
public void dispatchSurfaceRedrawNeededAsync(SurfaceHolder holder, SurfaceHolder.Callback callbacks[]) {
if (callbacks == null || callbacks.length == 0) {
- try {
- mSession.finishDrawing(mWindow);
- } catch (RemoteException e) {
- }
+ mRunnable.run();
return;
}
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index f221392..6e8c931 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -452,10 +452,6 @@
const RenderProperties& props = node.properties();
uirenderer::Rect bounds(props.getWidth(), props.getHeight());
transform.mapRect(bounds);
- bounds.left -= info.windowInsetLeft;
- bounds.right -= info.windowInsetLeft;
- bounds.top -= info.windowInsetTop;
- bounds.bottom -= info.windowInsetTop;
if (CC_LIKELY(transform.isPureTranslate())) {
// snap/round the computed bounds, so they match the rounding behavior
@@ -627,9 +623,9 @@
int register_android_view_RenderNode(JNIEnv* env) {
jclass clazz = FindClassOrDie(env, "android/view/SurfaceView");
gSurfaceViewPositionUpdateMethod = GetMethodIDOrDie(env, clazz,
- "updateWindowPosition_renderWorker", "(JIIII)V");
+ "updateSurfacePosition_renderWorker", "(JIIII)V");
gSurfaceViewPositionLostMethod = GetMethodIDOrDie(env, clazz,
- "windowPositionLost_uiRtSync", "(J)V");
+ "surfacePositionLost_uiRtSync", "(J)V");
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index a81901d..be86f5c 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -693,7 +693,6 @@
return JNI_TRUE;
}
-
static void nativeDeferTransactionUntil(JNIEnv* env, jclass clazz, jlong nativeObject,
jobject handleObject, jlong frameNumber) {
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
@@ -702,6 +701,27 @@
ctrl->deferTransactionUntil(handle, frameNumber);
}
+static void nativeDeferTransactionUntilSurface(JNIEnv* env, jclass clazz, jlong nativeObject,
+ jobject surfaceObject, jlong frameNumber) {
+ auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ sp<Surface> barrier = reinterpret_cast<Surface *>(surfaceObject);
+
+ ctrl->deferTransactionUntil(barrier, frameNumber);
+}
+
+static void nativeReparentChildren(JNIEnv* env, jclass clazz, jlong nativeObject,
+ jobject newParentObject) {
+ auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ sp<IBinder> handle = ibinderForJavaObject(env, newParentObject);
+
+ ctrl->reparentChildren(handle);
+}
+
+static void nativeSeverChildren(JNIEnv* env, jclass clazz, jlong nativeObject) {
+ auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ ctrl->detachChildren();
+}
+
static void nativeSetOverrideScalingMode(JNIEnv* env, jclass clazz, jlong nativeObject,
jint scalingMode) {
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
@@ -824,6 +844,12 @@
(void*)nativeSetDisplayPowerMode },
{"nativeDeferTransactionUntil", "(JLandroid/os/IBinder;J)V",
(void*)nativeDeferTransactionUntil },
+ {"nativeDeferTransactionUntilSurface", "(JJJ)V",
+ (void*)nativeDeferTransactionUntilSurface },
+ {"nativeReparentChildren", "(JLandroid/os/IBinder;)V",
+ (void*)nativeReparentChildren } ,
+ {"nativeSeverChildren", "(J)V",
+ (void*)nativeSeverChildren } ,
{"nativeSetOverrideScalingMode", "(JI)V",
(void*)nativeSetOverrideScalingMode },
{"nativeGetHandle", "(J)Landroid/os/IBinder;",
diff --git a/core/jni/android_view_SurfaceSession.cpp b/core/jni/android_view_SurfaceSession.cpp
index dad6958..508d897 100644
--- a/core/jni/android_view_SurfaceSession.cpp
+++ b/core/jni/android_view_SurfaceSession.cpp
@@ -24,6 +24,7 @@
#include <utils/RefBase.h>
#include <gui/SurfaceComposerClient.h>
+#include <gui/Surface.h>
namespace android {
@@ -45,6 +46,13 @@
return reinterpret_cast<jlong>(client);
}
+static jlong nativeCreateScoped(JNIEnv* env, jclass clazz, jlong surfaceObject) {
+ Surface *parent = reinterpret_cast<Surface*>(surfaceObject);
+ SurfaceComposerClient* client = new SurfaceComposerClient(parent->getIGraphicBufferProducer());
+ client->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(client);
+}
+
static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
SurfaceComposerClient* client = reinterpret_cast<SurfaceComposerClient*>(ptr);
client->decStrong((void*)nativeCreate);
@@ -55,11 +63,12 @@
client->dispose();
}
-
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "nativeCreate", "()J",
(void*)nativeCreate },
+ { "nativeCreateScoped", "(J)J",
+ (void*)nativeCreateScoped },
{ "nativeDestroy", "(J)V",
(void*)nativeDestroy },
{ "nativeKill", "(J)V",
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 37eae48a..99edf6e 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -178,13 +178,9 @@
}
}
// TODO: This is hacky
- info.windowInsetLeft = -stagingProperties().getLeft();
- info.windowInsetTop = -stagingProperties().getTop();
info.updateWindowPositions = true;
RenderNode::prepareTree(info);
info.updateWindowPositions = false;
- info.windowInsetLeft = 0;
- info.windowInsetTop = 0;
info.errorHandler = nullptr;
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c137ae2..55b8884 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2304,6 +2304,39 @@
<enum name="manual" value="2" />
</attr>
+ <!-- Describes the content of a view so that a auto-fill service can fill in the appropriate
+ data. Multiple flags can be combined to mean e.g. emailAddress or postalAddress. -->
+ <attr name="autoFillHint">
+ <!-- No hint. -->
+ <flag name="none" value="0" />
+ <!-- The view contains an email address. -->
+ <flag name="emailAddress" value="0x1" />
+ <!-- The view contains a real name. -->
+ <flag name="name" value="0x2" />
+ <!-- The view contains a user name. -->
+ <flag name="username" value="0x4" />
+ <!-- The view contains a password. -->
+ <flag name="password" value="0x8" />
+ <!-- The view contains a phone number. -->
+ <flag name="phone" value="0x10" />
+ <!-- The view contains a postal address. -->
+ <flag name="postalAddress" value="0x20" />
+ <!-- The view contains a postal code. -->
+ <flag name="postalCode" value="0x40" />
+ <!-- The view contains a credit card number. -->
+ <flag name="creditCardNumber" value="0x80" />
+ <!-- The view contains a credit card security code -->
+ <flag name="creditCardSecurityCode" value="0x100" />
+ <!-- The view contains a credit card expiration date -->
+ <flag name="creditCardExpirationDate" value="0x200" />
+ <!-- The view contains the month a credit card expires -->
+ <flag name="creditCardExpirationMonth" value="0x400" />
+ <!-- The view contains the year a credit card expires -->
+ <flag name="creditCardExpirationYear" value="0x800" />
+ <!-- The view contains the day a credit card expires -->
+ <flag name="creditCardExpirationDay" value="0x1000" />
+ </attr>
+
<!-- Boolean that controls whether a view can take focus while in touch mode.
If this is true for a view, that view can gain focus when clicked on, and can keep
focus if another view is clicked on that doesn't have this attribute set to true. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 15764a9..df3962c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2801,6 +2801,7 @@
<public name="secondaryContentAlpha" />
<public name="requiredFeature" />
<public name="requiredNotFeature" />
+ <public name="autoFillHint" />
</public-group>
<public-group type="style" first-id="0x010302e0">
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index b685431..c4bb72c 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -29,8 +29,8 @@
byte[] getCertificate(String alias);
byte[] getCaCertificates(String alias);
- // APIs used by CertInstaller
- void installCaCertificate(in byte[] caCertificate);
+ // APIs used by CertInstaller and DevicePolicyManager
+ String installCaCertificate(in byte[] caCertificate);
// APIs used by DevicePolicyManager
boolean installKeyPair(in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias);
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 2e33609..374c1b1 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -268,7 +268,7 @@
// Geometry
void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) {
- if (CC_UNLIKELY(floatCount < 2 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(floatCount < 2 || paint.nothingToDraw())) return;
floatCount &= ~0x1; // round down to nearest two
addOp(alloc().create_trivial<PointsOp>(
@@ -279,7 +279,7 @@
}
void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
- if (CC_UNLIKELY(floatCount < 4 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(floatCount < 4 || paint.nothingToDraw())) return;
floatCount &= ~0x3; // round down to nearest four
addOp(alloc().create_trivial<LinesOp>(
@@ -290,7 +290,7 @@
}
void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
addOp(alloc().create_trivial<RectOp>(
Rect(left, top, right, bottom),
@@ -333,7 +333,7 @@
}
void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
if (paint.getStyle() == SkPaint::kFill_Style
&& (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
@@ -362,7 +362,7 @@
void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) {
addOp(alloc().create_trivial<RoundRectOp>(
@@ -398,7 +398,7 @@
void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
// TODO: move to Canvas.h
- if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
drawOval(x - radius, y - radius, x + radius, y + radius, paint);
}
@@ -419,7 +419,7 @@
}
void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
addOp(alloc().create_trivial<OvalOp>(
Rect(left, top, right, bottom),
@@ -430,7 +430,7 @@
void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
if (fabs(sweepAngle) >= 360.0f) {
drawOval(left, top, right, bottom, paint);
@@ -445,7 +445,7 @@
}
void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
addOp(alloc().create_trivial<PathOp>(
Rect(path.getBounds()),
@@ -543,7 +543,7 @@
void RecordingCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
float boundsRight, float boundsBottom, float totalAdvance) {
- if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ if (!glyphs || !positions || glyphCount <= 0 || paint.nothingToDraw()) return;
glyphs = refBuffer<glyph_t>(glyphs, glyphCount);
positions = refBuffer<float>(positions, glyphCount * 2);
@@ -563,7 +563,7 @@
glyphs[0] = layout.getGlyphId(i);
float x = hOffset + layout.getX(i);
float y = vOffset + layout.getY(i);
- if (PaintUtils::paintWillNotDrawText(paint)) return;
+ if (paint.nothingToDraw()) return;
const uint16_t* tempGlyphs = refBuffer<glyph_t>(glyphs, 1);
addOp(alloc().create_trivial<TextOnPathOp>(
*(mState.currentSnapshot()->transform),
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index c57b1b3..51a7d00 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -443,7 +443,7 @@
void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint,
SkCanvas::PointMode mode) {
- if (CC_UNLIKELY(count < 2 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(count < 2 || paint.nothingToDraw())) return;
// convert the floats into SkPoints
count >>= 1; // now it is the number of points
std::unique_ptr<SkPoint[]> pts(new SkPoint[count]);
@@ -469,49 +469,49 @@
}
void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
- if (CC_UNLIKELY(count < 4 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(count < 4 || paint.nothingToDraw())) return;
this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
}
void SkiaCanvas::drawRect(float left, float top, float right, float bottom,
const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
mCanvas->drawRectCoords(left, top, right, bottom, paint);
}
void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
mCanvas->drawRegion(region, paint);
}
void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawRoundRect(rect, rx, ry, paint);
}
void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
- if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
mCanvas->drawCircle(x, y, radius, paint);
}
void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawOval(oval, paint);
}
void SkiaCanvas::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
}
void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
- if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+ if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect rect;
SkRRect roundRect;
if (path.isOval(&rect)) {
@@ -698,7 +698,7 @@
const SkPaint& paint, float x, float y,
float boundsLeft, float boundsTop, float boundsRight, float boundsBottom,
float totalAdvance) {
- if (!text || !positions || count <= 0 || PaintUtils::paintWillNotDrawText(paint)) return;
+ if (!text || !positions || count <= 0 || paint.nothingToDraw()) return;
// Set align to left for drawing, as we don't want individual
// glyphs centered or right-aligned; the offset above takes
// care of all alignment.
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index c6fbe2bd5..e39614b 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -91,8 +91,6 @@
LayerUpdateQueue* layerUpdateQueue = nullptr;
ErrorHandler* errorHandler = nullptr;
- int32_t windowInsetLeft = 0;
- int32_t windowInsetTop = 0;
bool updateWindowPositions = false;
struct Out {
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 0ac09ac..7fb75dc 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -18,6 +18,7 @@
#include <gtest/gtest.h>
#include <RecordingCanvas.h>
+#include <SkBlurDrawLooper.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
@@ -59,3 +60,21 @@
EXPECT_EQ(directOp->unmappedBounds, pictureOp->unmappedBounds);
EXPECT_EQ(directOp->localMatrix, pictureOp->localMatrix);
}
+
+TEST(SkiaCanvas, drawShadowLayer) {
+ auto surface = SkSurface::MakeRasterN32Premul(10, 10);
+ SkiaCanvas canvas(surface->getCanvas());
+
+ // clear to white
+ canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
+
+ SkPaint paint;
+ // it is transparent to ensure that we still draw the rect since it has a looper
+ paint.setColor(SK_ColorTRANSPARENT);
+ // this is how view's shadow layers are implemented
+ paint.setLooper(SkBlurDrawLooper::Make(0xF0000000, 6.0f, 0, 10));
+ canvas.drawRect(3, 3, 7, 7, paint);
+
+ ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
+ ASSERT_NE(TestUtils::getColor(surface, 5, 5), SK_ColorWHITE);
+}
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index 845a3ea..2673be1c 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -39,21 +39,6 @@
return GL_NEAREST;
}
- // TODO: move to a method on android:Paint? replace with SkPaint::nothingToDraw()?
- static inline bool paintWillNotDraw(const SkPaint& paint) {
- return paint.getAlpha() == 0
- && !paint.getColorFilter()
- && paint.getBlendMode() == SkBlendMode::kSrcOver;
- }
-
- // TODO: move to a method on android:Paint? replace with SkPaint::nothingToDraw()?
- static inline bool paintWillNotDrawText(const SkPaint& paint) {
- return paint.getAlpha() == 0
- && paint.getLooper() == nullptr
- && !paint.getColorFilter()
- && paint.getBlendMode() == SkBlendMode::kSrcOver;
- }
-
static bool isOpaquePaint(const SkPaint* paint) {
if (!paint) return true; // default (paintless) behavior is SrcOver, black
diff --git a/media/java/android/media/midi/MidiDevice.java b/media/java/android/media/midi/MidiDevice.java
index f79cd06..4b43260 100644
--- a/media/java/android/media/midi/MidiDevice.java
+++ b/media/java/android/media/midi/MidiDevice.java
@@ -35,6 +35,10 @@
* Instances of this class are created by {@link MidiManager#openDevice}.
*/
public final class MidiDevice implements Closeable {
+ static {
+ System.loadLibrary("media_jni");
+ }
+
private static final String TAG = "MidiDevice";
private final MidiDeviceInfo mDeviceInfo;
@@ -43,6 +47,7 @@
private final IBinder mClientToken;
private final IBinder mDeviceToken;
private boolean mIsDeviceClosed;
+ private boolean mIsMirroredToNative;
private final CloseGuard mGuard = CloseGuard.get();
@@ -209,10 +214,45 @@
}
}
+ /**
+ * Makes Midi Device available to the Native API
+ * @hide
+ */
+ public void mirrorToNative() throws IOException {
+ if (mIsDeviceClosed || mIsMirroredToNative) {
+ return;
+ }
+
+ int result = mirrorToNative(mDeviceServer.asBinder(), mDeviceInfo.getId());
+ if (result != 0) {
+ throw new IOException("Failed mirroring to native: " + result);
+ }
+
+ mIsMirroredToNative = true;
+ }
+
+ /**
+ * Makes Midi Device no longer available to the Native API
+ * @hide
+ */
+ public void removeFromNative() throws IOException {
+ if (!mIsMirroredToNative) {
+ return;
+ }
+
+ int result = removeFromNative(mDeviceInfo.getId());
+ if (result != 0) {
+ throw new IOException("Failed removing from native: " + result);
+ }
+
+ mIsMirroredToNative = false;
+ }
+
@Override
public void close() throws IOException {
synchronized (mGuard) {
if (!mIsDeviceClosed) {
+ removeFromNative();
mGuard.close();
mIsDeviceClosed = true;
try {
@@ -238,4 +278,7 @@
public String toString() {
return ("MidiDevice: " + mDeviceInfo.toString());
}
+
+ private native int mirrorToNative(IBinder deviceServerBinder, int uid);
+ private native int removeFromNative(int uid);
}
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index aee9d38e..e5af357 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -776,8 +776,8 @@
mSurface = null;
mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
@Override
- protected void updateWindow() {
- super.updateWindow();
+ protected void updateSurface() {
+ super.updateSurface();
relayoutSessionOverlayView();
}};
// The surface view's content should be treated as secure all the time.
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 861ed0a..23bf3d6 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -26,6 +26,7 @@
android_mtp_MtpDatabase.cpp \
android_mtp_MtpDevice.cpp \
android_mtp_MtpServer.cpp \
+ midi/android_media_midi_MidiDevice.cpp \
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
@@ -34,6 +35,7 @@
libbinder \
libmedia \
libmediadrm \
+ libmidi \
libskia \
libui \
liblog \
@@ -55,6 +57,7 @@
external/tremor/Tremor \
frameworks/base/core/jni \
frameworks/base/libs/hwui \
+ frameworks/base/media/native \
frameworks/av/media/libmedia \
frameworks/av/media/libstagefright \
frameworks/av/media/mtp \
diff --git a/media/jni/midi/android_media_midi_MidiDevice.cpp b/media/jni/midi/android_media_midi_MidiDevice.cpp
new file mode 100644
index 0000000..1e54bac
--- /dev/null
+++ b/media/jni/midi/android_media_midi_MidiDevice.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Midi-JNI"
+
+#include <android_util_Binder.h>
+#include <midi/MidiDeviceRegistry.h>
+#include <nativehelper/jni.h>
+#include <utils/Log.h>
+
+using namespace android;
+using namespace android::media::midi;
+
+extern "C" jint Java_android_media_midi_MidiDevice_mirrorToNative(
+ JNIEnv *env, jobject thiz, jobject midiDeviceServer, jint id)
+{
+ (void)thiz;
+ sp<IBinder> serverBinder = ibinderForJavaObject(env, midiDeviceServer);
+ if (serverBinder.get() == NULL) {
+ ALOGE("Could not obtain IBinder from passed jobject");
+ return -EINVAL;
+ }
+ // return MidiDeviceManager::getInstance().addDevice(serverBinder, uid);
+ return MidiDeviceRegistry::getInstance().addDevice(
+ new BpMidiDeviceServer(serverBinder), id);
+}
+
+extern "C" jint Java_android_media_midi_MidiDevice_removeFromNative(
+ JNIEnv *env, jobject thiz, jint uid)
+{
+ (void)env;
+ (void)thiz;
+ // return MidiDeviceManager::getInstance().removeDevice(uid);
+ return MidiDeviceRegistry::getInstance().removeDevice(uid);
+}
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
new file mode 100644
index 0000000..3500805
--- /dev/null
+++ b/media/native/midi/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 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.
+
+// The headers module is in frameworks/media/native/midi/Android.bp.
+ndk_library {
+ name: "libmidi.ndk",
+ symbol_file: "libmidi.map.txt",
+ first_version: "26",
+// unversioned_until: "current",
+}
diff --git a/media/native/midi/Android.mk b/media/native/midi/Android.mk
new file mode 100644
index 0000000..b91c430
--- /dev/null
+++ b/media/native/midi/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ ../../java/android/media/midi/IMidiDeviceServer.aidl \
+ midi.cpp \
+ MidiDeviceRegistry.cpp \
+ MidiPortRegistry.cpp
+
+LOCAL_AIDL_INCLUDES := \
+ $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
+ frameworks/native/aidl/binder
+
+LOCAL_CFLAGS += -Wall -Werror -O0
+
+LOCAL_MODULE := libmidi
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := liblog libbinder libutils libmedia
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/native/midi/MidiDeviceRegistry.cpp b/media/native/midi/MidiDeviceRegistry.cpp
new file mode 100644
index 0000000..8854a08
--- /dev/null
+++ b/media/native/midi/MidiDeviceRegistry.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MidiDeviceRegistry.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiDeviceRegistry);
+
+namespace media {
+namespace midi {
+
+MidiDeviceRegistry::MidiDeviceRegistry() : mNextDeviceToken(1) {
+}
+
+status_t MidiDeviceRegistry::addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId) {
+ if (server.get() == nullptr) {
+ return -EINVAL;
+ }
+
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ mServers[deviceId] = server;
+ return OK;
+}
+
+status_t MidiDeviceRegistry::removeDevice(int32_t deviceId) {
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ mServers.erase(deviceId);
+ const auto& iter = mUidToToken.find(deviceId);
+ if (iter != mUidToToken.end()) {
+ mTokenToUid.erase(iter->second);
+ mUidToToken.erase(iter);
+ }
+ return OK;
+}
+
+//NOTE: This creates an entry if not found, or returns an existing one.
+status_t MidiDeviceRegistry::obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr) {
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ const auto& serversIter = mServers.find(deviceId);
+ if (serversIter == mServers.end()) {
+ // Not found.
+ return -EINVAL;
+ }
+
+ const auto& iter = mUidToToken.find(deviceId);
+ if (iter != mUidToToken.end()) {
+ *deviceTokenPtr = iter->second;
+ } else {
+ *deviceTokenPtr = mNextDeviceToken++;
+ mTokenToUid[*deviceTokenPtr] = deviceId;
+ mUidToToken[deviceId] = *deviceTokenPtr;
+ }
+ return OK;
+}
+
+status_t MidiDeviceRegistry::releaseDevice(AMIDI_Device deviceToken) {
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ const auto& iter = mTokenToUid.find(deviceToken);
+ if (iter == mTokenToUid.end()) {
+ // Not found
+ return -EINVAL;
+ }
+
+ mServers.erase(iter->second);
+ mUidToToken.erase(iter->second);
+ mTokenToUid.erase(iter);
+ return OK;
+}
+
+status_t MidiDeviceRegistry::getDeviceByToken(
+ AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr) {
+ std::lock_guard<std::mutex> guard(mMapsLock);
+ int32_t id = -1;
+ {
+ const auto& iter = mTokenToUid.find(deviceToken);
+ if (iter == mTokenToUid.end()) {
+ return -EINVAL;
+ }
+ id = iter->second;
+ }
+ const auto& iter = mServers.find(id);
+ if (iter == mServers.end()) {
+ return -EINVAL;
+ }
+
+ *devicePtr = iter->second;
+ return OK;
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiDeviceRegistry.h b/media/native/midi/MidiDeviceRegistry.h
new file mode 100644
index 0000000..93be733
--- /dev/null
+++ b/media/native/midi/MidiDeviceRegistry.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
+#define ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
+
+#include <map>
+#include <mutex>
+
+#include <binder/IBinder.h>
+#include <utils/Errors.h>
+#include <utils/Singleton.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "midi.h"
+
+namespace android {
+namespace media {
+namespace midi {
+
+/*
+ * Maintains a thread-safe, (singleton) list of MIDI devices with associated Binder interfaces,
+ * which are exposed to the Native API via (Java) MidiDevice.mirrorToNative() &
+ * MidiDevice.removeFromNative().
+ * (Called via MidiDeviceManager::addDevice() MidiManager::removeDevice()).
+ */
+class MidiDeviceRegistry : public Singleton<MidiDeviceRegistry> {
+ public:
+ /* Add a MIDI Device to the registry.
+ *
+ * server The Binder interface to the MIDI device server.
+ * deviceUId The unique ID of the device obtained from
+ * the Java API via MidiDeviceInfo.getId().
+ */
+ status_t addDevice(sp<BpMidiDeviceServer> server, int32_t deviceId);
+
+ /* Remove the device (and associated server) from the Device registry.
+ *
+ * deviceUid The ID of the device which was used in the call to addDevice().
+ */
+ status_t removeDevice(int32_t deviceId);
+
+ /* Gets a device token associated with the device ID. This is used by the
+ * native API to identify/access the device.
+ * Multiple calls without releasing the token will return the same value.
+ *
+ * deviceUid The ID of the device.
+ * deviceTokenPtr Receives the device (native) token associated with the device ID.
+ * returns: OK on success, error code otherwise.
+ */
+ status_t obtainDeviceToken(int32_t deviceId, AMIDI_Device *deviceTokenPtr);
+
+ /*
+ * Releases the native API device token associated with a MIDI device.
+ *
+ * deviceToken The device (native) token associated with the device ID.
+ */
+ status_t releaseDevice(AMIDI_Device deviceToken);
+
+ /*
+ * Gets the Device server binder interface associated with the device token.
+ *
+ * deviceToken The device (native) token associated with the device ID.
+ */
+ status_t getDeviceByToken(AMIDI_Device deviceToken, sp<BpMidiDeviceServer> *devicePtr);
+
+ private:
+ friend class Singleton<MidiDeviceRegistry>;
+ MidiDeviceRegistry();
+
+ // Access Mutex
+ std::mutex mMapsLock;
+
+ // maps device IDs to servers
+ std::map<int32_t, sp<BpMidiDeviceServer>> mServers;
+
+ // maps device tokens to device ID
+ std::map<AMIDI_Device, int32_t> mTokenToUid;
+
+ // maps device IDs to device tokens
+ std::map<int32_t, AMIDI_Device> mUidToToken;
+
+ // Value of next device token to dole out.
+ AMIDI_Device mNextDeviceToken;
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_DEVICE_REGISTRY_H_
diff --git a/media/native/midi/MidiPortRegistry.cpp b/media/native/midi/MidiPortRegistry.cpp
new file mode 100644
index 0000000..fa70af8
--- /dev/null
+++ b/media/native/midi/MidiPortRegistry.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "MidiPortRegistry.h"
+
+namespace android {
+
+ANDROID_SINGLETON_STATIC_INSTANCE(media::midi::MidiPortRegistry);
+
+namespace media {
+namespace midi {
+
+//TODO Note that these 2 are identical
+struct MidiPortRegistry::OutputPort {
+ AMIDI_Device device;
+ sp<IBinder> binderToken;
+ base::unique_fd ufd;
+};
+
+struct MidiPortRegistry::InputPort {
+ AMIDI_Device device;
+ sp<IBinder> binderToken;
+ base::unique_fd ufd;
+};
+
+MidiPortRegistry::MidiPortRegistry() : mNextOutputPortToken(0), mNextInputPortToken(0) {
+}
+
+status_t MidiPortRegistry::addOutputPort(
+ AMIDI_Device device,
+ sp<IBinder> portToken,
+ base::unique_fd &&ufd,
+ AMIDI_OutputPort *portPtr) {
+ *portPtr = mNextOutputPortToken++;
+
+ OutputPortEntry* portEntry = new OutputPortEntry;
+ portEntry->port = new OutputPort;
+ portEntry->state = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+ portEntry->port = new OutputPort;
+ portEntry->port->device = device;
+ portEntry->port->binderToken = portToken;
+ portEntry->port->ufd = std::move(ufd);
+
+ mOutputPortMap[*portPtr] = portEntry;
+
+ return OK;
+}
+
+status_t MidiPortRegistry::removeOutputPort(
+ AMIDI_OutputPort port,
+ AMIDI_Device *devicePtr,
+ sp<IBinder> *portTokenPtr) {
+ OutputPortMap::iterator itr = mOutputPortMap.find(port);
+ if (itr == mOutputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ OutputPortEntry *entry = mOutputPortMap[port];
+ int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+ while (!entry->state.compare_exchange_weak(portState, MIDI_OUTPUT_PORT_STATE_CLOSED)) {
+ if (portState == MIDI_OUTPUT_PORT_STATE_CLOSED) {
+ return -EINVAL; // Already closed
+ }
+ }
+ *devicePtr = entry->port->device;
+ *portTokenPtr = entry->port->binderToken;
+ delete entry->port;
+ entry->port = nullptr;
+
+ mOutputPortMap.erase(itr);
+
+ return OK;
+}
+
+status_t MidiPortRegistry::getOutputPortFdAndLock(
+ AMIDI_OutputPort port, base::unique_fd **ufdPtr) {
+ if (mOutputPortMap.find(port) == mOutputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ OutputPortEntry *entry = mOutputPortMap[port];
+ int portState = MIDI_OUTPUT_PORT_STATE_OPEN_IDLE;
+ if (!entry->state.compare_exchange_strong(portState, MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE)) {
+ // The port has been closed.
+ return -EPIPE;
+ }
+ *ufdPtr = &entry->port->ufd;
+
+ return OK;
+}
+
+status_t MidiPortRegistry::unlockOutputPort(AMIDI_OutputPort port) {
+ if (mOutputPortMap.find(port) == mOutputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ OutputPortEntry *entry = mOutputPortMap[port];
+ entry->state.store(MIDI_OUTPUT_PORT_STATE_OPEN_IDLE);
+ return OK;
+}
+
+status_t MidiPortRegistry::addInputPort(
+ AMIDI_Device device,
+ sp<IBinder> portToken,
+ base::unique_fd &&ufd,
+ AMIDI_InputPort *portPtr) {
+ *portPtr = mNextInputPortToken++;
+
+ InputPortEntry *entry = new InputPortEntry;
+
+ entry->state = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+ entry->port = new InputPort;
+ entry->port->device = device;
+ entry->port->binderToken = portToken;
+ entry->port->ufd = std::move(ufd);
+
+ mInputPortMap[*portPtr] = entry;
+
+ return OK;
+}
+
+status_t MidiPortRegistry::removeInputPort(
+ AMIDI_InputPort port,
+ AMIDI_Device *devicePtr,
+ sp<IBinder> *portTokenPtr) {
+ InputPortMap::iterator itr = mInputPortMap.find(port);
+ if (itr == mInputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ InputPortEntry *entry = mInputPortMap[port];
+ int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+ while (!entry->state.compare_exchange_weak(portState, MIDI_INPUT_PORT_STATE_CLOSED)) {
+ if (portState == MIDI_INPUT_PORT_STATE_CLOSED) return -EINVAL; // Already closed
+ }
+
+ *devicePtr = entry->port->device;
+ *portTokenPtr = entry->port->binderToken;
+ delete entry->port;
+ entry->port = nullptr;
+
+ mInputPortMap.erase(itr);
+
+ return OK;
+}
+
+status_t MidiPortRegistry::getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr) {
+ if (mInputPortMap.find(port) == mInputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ InputPortEntry *entry = mInputPortMap[port];
+
+ *ufdPtr = &entry->port->ufd;
+
+ return OK;
+}
+
+status_t MidiPortRegistry::getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr) {
+ if (mInputPortMap.find(port) == mInputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ InputPortEntry *entry = mInputPortMap[port];
+
+ int portState = MIDI_INPUT_PORT_STATE_OPEN_IDLE;
+ if (!entry->state.compare_exchange_strong(portState, MIDI_INPUT_PORT_STATE_OPEN_ACTIVE)) {
+ // The port has been closed.
+ return -EPIPE;
+ }
+ *ufdPtr = &entry->port->ufd;
+ return OK;
+}
+
+status_t MidiPortRegistry::MidiPortRegistry::unlockInputPort(AMIDI_InputPort port) {
+ if (mInputPortMap.find(port) == mInputPortMap.end()) {
+ return -EINVAL;
+ }
+
+ InputPortEntry *entry = mInputPortMap[port];
+ entry->state.store(MIDI_INPUT_PORT_STATE_OPEN_IDLE);
+ return OK;
+}
+
+} // namespace midi
+} // namespace media
+} // namespace android
diff --git a/media/native/midi/MidiPortRegistry.h b/media/native/midi/MidiPortRegistry.h
new file mode 100644
index 0000000..f1ffb78
--- /dev/null
+++ b/media/native/midi/MidiPortRegistry.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
+#define ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
+
+#include <atomic>
+#include <map>
+
+#include <android-base/unique_fd.h>
+#include <binder/IBinder.h>
+#include <utils/Errors.h>
+#include <utils/Singleton.h>
+
+#include "midi.h"
+
+namespace android {
+namespace media {
+namespace midi {
+
+/*
+ * Maintains lists of all active input and output MIDI ports and controls access to them. Provides
+ * exclusive access to specific MIDI ports.
+ */
+class MidiPortRegistry : public Singleton<MidiPortRegistry> {
+ public:
+ /*
+ * Creates an output port entry and associates it with the specified MIDI device.
+ * Called by AMIDI_openOutputPort();
+ *
+ * device The native API device ID.
+ * portToken The port token (returned from the device server).
+ * udf File descriptor for the data port associated with the MIDI output port.
+ * portPtr Receives the native API port ID of the port being opened.
+ */
+ status_t addOutputPort(
+ AMIDI_Device device,
+ sp<IBinder> portToken,
+ base::unique_fd &&ufd,
+ AMIDI_OutputPort *portPtr);
+
+ /*
+ * Removes for the output port list a previously added output port.
+ * Called by AMIDI_closeOutputPort();
+ *
+ * port The native API port ID of the port being closed.
+ * devicePtr Receives the native API device ID associated with the port.
+ * portTokenPtr Receives the binder token associated with the port.
+ */
+ status_t removeOutputPort(
+ AMIDI_OutputPort port,
+ AMIDI_Device *devicePtr,
+ sp<IBinder> *portTokenPtr);
+
+ /*
+ * Creates an input port entry and associates it with the specified MIDI device.
+ * Called by AMIDI_openInputPort();
+ *
+ * device The native API device ID.
+ * portToken The port token (returned from the device server).
+ * udf File descriptor for the data port associated with the MIDI input port.
+ * portPtr Receives the native API port ID of the port being opened.
+ */
+ status_t addInputPort(
+ AMIDI_Device device,
+ sp<IBinder> portToken,
+ base::unique_fd &&ufd,
+ AMIDI_InputPort *portPtr);
+
+ /*
+ * Removes for the input port list a previously added input port.
+ * Called by AMIDI_closeINputPort();
+ *
+ * port The native API port ID of the port being closed.
+ * devicePtr Receives the native API device ID associated with the port.
+ * portTokenPtr Receives the binder token associated with the port.
+ */
+ status_t removeInputPort(
+ AMIDI_InputPort port,
+ AMIDI_Device *devicePtr,
+ sp<IBinder> *portTokenPtr);
+
+ /*
+ * Retrieves an exclusive-access file descriptor for an output port.
+ * Called from AMIDI_receive().
+ *
+ * port The native API id of the output port.
+ * ufdPtr Receives the exclusive-access file descriptor for the output port.
+ */
+ status_t getOutputPortFdAndLock(AMIDI_OutputPort port, base::unique_fd **ufdPtr);
+
+ /*
+ * Releases exclusive-access to the port and invalidates the previously received file
+ * descriptor.
+ * Called from AMIDI_receive().
+ *
+ * port The native API id of the output port.
+ */
+ status_t unlockOutputPort(AMIDI_OutputPort port);
+
+ /*
+ * Retrieves an exclusive-access file descriptor for an input port.
+ * (Not being used as (perhaps) AMIDI_sendWithTimestamp() doesn't need exclusive access
+ * to the port).
+ *
+ * port The native API id of the input port.
+ * ufdPtr Receives the exclusive-access file descriptor for the input port.
+ */
+ status_t getInputPortFdAndLock(AMIDI_InputPort port, base::unique_fd **ufdPtr);
+
+ /*
+ * Releases exclusive-access to the port and invalidates the previously received file
+ * descriptor.
+ * (Not used. See above).
+ *
+ * port The native API id of the input port.
+ */
+ status_t unlockInputPort(AMIDI_InputPort port);
+
+ /*
+ * Retrieves an unlocked (multi-access) file descriptor for an input port.
+ * Used by AMIDI_sendWith(), AMIDI_sendWithTimestamp & AMIDI_flush.
+ *
+ * port The native API id of the input port.
+ * ufdPtr Receives the multi-access file descriptor for the input port.
+ */
+ status_t getInputPortFd(AMIDI_InputPort port, base::unique_fd **ufdPtr);
+
+ private:
+ friend class Singleton<MidiPortRegistry>;
+ MidiPortRegistry();
+
+ /*
+ * Output (data receiving) ports.
+ */
+ struct OutputPort;
+ enum {
+ MIDI_OUTPUT_PORT_STATE_CLOSED = 0,
+ MIDI_OUTPUT_PORT_STATE_OPEN_IDLE,
+ MIDI_OUTPUT_PORT_STATE_OPEN_ACTIVE
+ };
+
+ struct OutputPortEntry {
+ std::atomic_int state;
+ OutputPort *port;
+ };
+
+ typedef std::map<AMIDI_OutputPort, OutputPortEntry*> OutputPortMap;
+ // Access is synchronized per record via 'state' field.
+ std::atomic<AMIDI_OutputPort> mNextOutputPortToken;
+ OutputPortMap mOutputPortMap;
+
+ /*
+ * Input (data sending) ports.
+ */
+ struct InputPort;
+ enum {
+ MIDI_INPUT_PORT_STATE_CLOSED = 0,
+ MIDI_INPUT_PORT_STATE_OPEN_IDLE,
+ MIDI_INPUT_PORT_STATE_OPEN_ACTIVE
+ };
+
+ struct InputPortEntry {
+ std::atomic_int state;
+ InputPort *port;
+ };
+
+ typedef std::map<AMIDI_OutputPort, InputPortEntry*> InputPortMap;
+ // Access is synchronized per record via 'state' field.
+ std::atomic<AMIDI_InputPort> mNextInputPortToken;
+ InputPortMap mInputPortMap;
+
+};
+
+} // namespace midi
+} // namespace media
+} // namespace android
+
+#endif // ANDROID_MEDIA_MIDI_PORT_REGISTRY_H_
diff --git a/media/native/midi/midi.cpp b/media/native/midi/midi.cpp
new file mode 100644
index 0000000..1bf0bd0
--- /dev/null
+++ b/media/native/midi/midi.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#define LOG_TAG "NativeMIDI"
+
+#include <poll.h>
+#include <unistd.h>
+
+#include <binder/Binder.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+
+#include "android/media/midi/BpMidiDeviceServer.h"
+#include "media/MidiDeviceInfo.h"
+#include "MidiDeviceRegistry.h"
+#include "MidiPortRegistry.h"
+
+#include "midi.h"
+
+using android::IBinder;
+using android::BBinder;
+using android::OK;
+using android::sp;
+using android::status_t;
+using android::base::unique_fd;
+using android::binder::Status;
+using android::media::midi::BpMidiDeviceServer;
+using android::media::midi::MidiDeviceInfo;
+using android::media::midi::MidiDeviceRegistry;
+using android::media::midi::MidiPortRegistry;
+
+#define SIZE_MIDIRECEIVEBUFFER AMIDI_BUFFER_SIZE
+
+/* TRANSFER PACKET FORMAT (as defined in MidiPortImpl.java)
+ *
+ * Transfer packet format is as follows (see MidiOutputPort.mThread.run() to see decomposition):
+ * |oc|md|md| ......... |md|ts|ts|ts|ts|ts|ts|ts|ts|
+ * ^ +--------------------+-----------------------+
+ * | ^ ^
+ * | | |
+ * | | + timestamp (8 bytes)
+ * | |
+ * | + MIDI data bytes (numBytes bytes)
+ * |
+ * + OpCode (AMIDI_OPCODE_DATA)
+ *
+ * NOTE: The socket pair is configured to use SOCK_SEQPACKET mode.
+ * SOCK_SEQPACKET, for a sequenced-packet socket that is connection-oriented, preserves message
+ * boundaries, and delivers messages in the order that they were sent.
+ * So 'read()' always returns a whole message.
+ */
+
+status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr) {
+ return MidiDeviceRegistry::getInstance().obtainDeviceToken(id, devicePtr);
+}
+
+status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr) {
+ sp<BpMidiDeviceServer> deviceServer;
+ status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ ALOGE("AMIDI_getDeviceInfo bad device token %d: %d", device, result);
+ return result;
+ }
+
+ MidiDeviceInfo deviceInfo;
+ Status txResult = deviceServer->getDeviceInfo(&deviceInfo);
+ if (!txResult.isOk()) {
+ ALOGE("AMIDI_getDeviceInfo transaction error: %d", txResult.transactionError());
+ return txResult.transactionError();
+ }
+
+ deviceInfoPtr->type = deviceInfo.getType();
+ deviceInfoPtr->uid = deviceInfo.getUid();
+ deviceInfoPtr->isPrivate = deviceInfo.isPrivate();
+ deviceInfoPtr->inputPortCount = deviceInfo.getInputPortNames().size();
+ deviceInfoPtr->outputPortCount = deviceInfo.getOutputPortNames().size();
+ return OK;
+}
+
+/*
+ * Output (receiving) API
+ */
+status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr) {
+ sp<BpMidiDeviceServer> deviceServer;
+ status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ ALOGE("AMIDI_openOutputPort bad device token %d: %d", device, result);
+ return result;
+ }
+
+ sp<BBinder> portToken(new BBinder());
+ unique_fd ufd;
+ Status txResult = deviceServer->openOutputPort(portToken, portNumber, &ufd);
+ if (!txResult.isOk()) {
+ ALOGE("AMIDI_openOutputPort transaction error: %d", txResult.transactionError());
+ return txResult.transactionError();
+ }
+
+ result = MidiPortRegistry::getInstance().addOutputPort(
+ device, portToken, std::move(ufd), outputPortPtr);
+ if (result != OK) {
+ ALOGE("AMIDI_openOutputPort port registration error: %d", result);
+ // Close port
+ return result;
+ }
+ return OK;
+}
+
+ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages) {
+ unique_fd *ufd;
+ // TODO: May return a nicer self-unlocking object
+ status_t result = MidiPortRegistry::getInstance().getOutputPortFdAndLock(outputPort, &ufd);
+ if (result != OK) {
+ return result;
+ }
+
+ ssize_t messagesRead = 0;
+ while (messagesRead < maxMessages) {
+ struct pollfd checkFds[1] = { { *ufd, POLLIN, 0 } };
+ int pollResult = poll(checkFds, 1, 0);
+ if (pollResult < 1) {
+ result = android::INVALID_OPERATION;
+ break;
+ }
+
+ AMIDI_Message *message = &messages[messagesRead];
+ uint8_t readBuffer[AMIDI_PACKET_SIZE];
+ memset(readBuffer, 0, sizeof(readBuffer));
+ ssize_t readCount = read(*ufd, readBuffer, sizeof(readBuffer));
+ if (readCount == EINTR) {
+ continue;
+ }
+ if (readCount < 1) {
+ result = android::NOT_ENOUGH_DATA;
+ break;
+ }
+
+ // set Packet Format definition at the top of this file.
+ size_t dataSize = 0;
+ message->opcode = readBuffer[0];
+ message->timestamp = 0;
+ if (message->opcode == AMIDI_OPCODE_DATA && readCount >= AMIDI_PACKET_OVERHEAD) {
+ dataSize = readCount - AMIDI_PACKET_OVERHEAD;
+ if (dataSize) {
+ memcpy(message->buffer, readBuffer + 1, dataSize);
+ }
+ message->timestamp = *(uint64_t*) (readBuffer + readCount - sizeof(uint64_t));
+ }
+ message->len = dataSize;
+ ++messagesRead;
+ }
+
+ MidiPortRegistry::getInstance().unlockOutputPort(outputPort);
+ return result == OK ? messagesRead : result;
+}
+
+status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort) {
+ AMIDI_Device device;
+ sp<IBinder> portToken;
+ status_t result =
+ MidiPortRegistry::getInstance().removeOutputPort(outputPort, &device, &portToken);
+ if (result != OK) {
+ return result;
+ }
+
+ sp<BpMidiDeviceServer> deviceServer;
+ result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ return result;
+ }
+
+ Status txResult = deviceServer->closePort(portToken);
+ if (!txResult.isOk()) {
+ return txResult.transactionError();
+ }
+ return OK;
+}
+
+/*
+ * Input (sending) API
+ */
+status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr) {
+ sp<BpMidiDeviceServer> deviceServer;
+ status_t result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ ALOGE("AMIDI_openInputPort bad device token %d: %d", device, result);
+ return result;
+ }
+
+ sp<BBinder> portToken(new BBinder());
+ unique_fd ufd; // this is the file descriptor of the "receive" port s
+ Status txResult = deviceServer->openInputPort(portToken, portNumber, &ufd);
+ if (!txResult.isOk()) {
+ ALOGE("AMIDI_openInputPort transaction error: %d", txResult.transactionError());
+ return txResult.transactionError();
+ }
+
+ result = MidiPortRegistry::getInstance().addInputPort(
+ device, portToken, std::move(ufd), inputPortPtr);
+ if (result != OK) {
+ ALOGE("AMIDI_openInputPort port registration error: %d", result);
+ // Close port
+ return result;
+ }
+
+ return OK;
+}
+
+status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort) {
+ AMIDI_Device device;
+ sp<IBinder> portToken;
+ status_t result = MidiPortRegistry::getInstance().removeInputPort(
+ inputPort, &device, &portToken);
+ if (result != OK) {
+ ALOGE("AMIDI_closeInputPort remove port error: %d", result);
+ return result;
+ }
+
+ sp<BpMidiDeviceServer> deviceServer;
+ result = MidiDeviceRegistry::getInstance().getDeviceByToken(device, &deviceServer);
+ if (result != OK) {
+ ALOGE("AMIDI_closeInputPort can't find device error: %d", result);
+ return result;
+ }
+
+ Status txResult = deviceServer->closePort(portToken);
+ if (!txResult.isOk()) {
+ result = txResult.transactionError();
+ ALOGE("AMIDI_closeInputPort transaction error: %d", result);
+ return result;
+ }
+
+ return OK;
+}
+
+ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort /*inputPort*/) {
+ return SIZE_MIDIRECEIVEBUFFER;
+}
+
+static ssize_t AMIDI_makeSendBuffer(uint8_t *buffer, uint8_t *data, ssize_t numBytes, uint64_t timestamp) {
+ buffer[0] = AMIDI_OPCODE_DATA;
+ memcpy(buffer + 1, data, numBytes);
+ memcpy(buffer + 1 + numBytes, ×tamp, sizeof(timestamp));
+ return numBytes + AMIDI_PACKET_OVERHEAD;
+}
+
+// Handy debugging function.
+//static void AMIDI_logBuffer(uint8_t *data, size_t numBytes) {
+// for (size_t index = 0; index < numBytes; index++) {
+// ALOGI(" data @%zu [0x%X]", index, data[index]);
+// }
+//}
+
+ssize_t AMIDI_send(AMIDI_InputPort inputPort, uint8_t *buffer, ssize_t numBytes) {
+ return AMIDI_sendWithTimestamp(inputPort, buffer, numBytes, 0);
+}
+
+ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort inputPort, uint8_t *data,
+ ssize_t numBytes, int64_t timestamp) {
+
+ if (numBytes > SIZE_MIDIRECEIVEBUFFER) {
+ return android::BAD_VALUE;
+ }
+
+ // AMIDI_logBuffer(data, numBytes);
+
+ unique_fd *ufd = NULL;
+ status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd);
+ if (result != OK) {
+ return result;
+ }
+
+ uint8_t writeBuffer[SIZE_MIDIRECEIVEBUFFER + AMIDI_PACKET_OVERHEAD];
+ ssize_t numTransferBytes = AMIDI_makeSendBuffer(writeBuffer, data, numBytes, timestamp);
+ ssize_t numWritten = write(*ufd, writeBuffer, numTransferBytes);
+
+ if (numWritten < numTransferBytes) {
+ ALOGE("AMIDI_sendWithTimestamp Couldn't write MIDI data buffer. requested:%zu, written%zu",
+ numTransferBytes, numWritten);
+ }
+
+ return numWritten - AMIDI_PACKET_OVERHEAD;
+}
+
+status_t AMIDI_flush(AMIDI_InputPort inputPort) {
+ unique_fd *ufd = NULL;
+ status_t result = MidiPortRegistry::getInstance().getInputPortFd(inputPort, &ufd);
+ if (result != OK) {
+ return result;
+ }
+
+ uint8_t opCode = AMIDI_OPCODE_FLUSH;
+ ssize_t numTransferBytes = 1;
+ ssize_t numWritten = write(*ufd, &opCode, numTransferBytes);
+
+ if (numWritten < numTransferBytes) {
+ ALOGE("AMIDI_flush Couldn't write MIDI flush. requested:%zu, written%zu",
+ numTransferBytes, numWritten);
+ return android::INVALID_OPERATION;
+ }
+
+ return OK;
+}
+
diff --git a/media/native/midi/midi.h b/media/native/midi/midi.h
new file mode 100644
index 0000000..717bc66
--- /dev/null
+++ b/media/native/midi/midi.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_MIDI_H_
+#define ANDROID_MEDIA_MIDI_H_
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+
+using android::status_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//typedef struct _AMIDI_Device;
+//typedef struct _AMIDI_InputPort;
+//typedef struct _AMIDI_OutputPort;
+//typedef _AMIDI_Device* AMIDI_Device;
+//typedef _AMIDI_InputPort* AMIDI_InputPort;
+//typedef _AMIDI_OutputPort* AMIDI_OutputPort;
+
+typedef int32_t AMIDI_Device;
+typedef int32_t AMIDI_InputPort;
+typedef int32_t AMIDI_OutputPort;
+
+//TODO - Do we want to wrap this stuff in namespace android { namespace media { namespace midi {?
+
+enum {
+ AMIDI_INVALID_HANDLE = -1
+};
+
+enum {
+ AMIDI_OPCODE_DATA = 1,
+ AMIDI_OPCODE_FLUSH = 2,
+ AMIDI_PACKET_SIZE = 1024, /* !!! Currently MidiPortImpl.MAX_PACKET_SIZE !!! */
+ AMIDI_PACKET_OVERHEAD = 9,
+ AMIDI_BUFFER_SIZE = AMIDI_PACKET_SIZE - AMIDI_PACKET_OVERHEAD
+ /* !!! TBD, currently MidiPortImpl.MAX_PACKET_DATA_SIZE !!! */
+};
+
+typedef struct {
+ uint32_t opcode;
+ uint8_t buffer[AMIDI_BUFFER_SIZE];
+ size_t len;
+ int64_t timestamp;
+} AMIDI_Message;
+
+enum {
+ AMIDI_DEVICE_TYPE_USB = 1,
+ AMIDI_DEVICE_TYPE_VIRTUAL = 2,
+ AMIDI_DEVICE_TYPE_BLUETOOTH = 3
+};
+
+typedef struct {
+ int32_t type;
+ int32_t uid;
+ int32_t isPrivate;
+ int32_t inputPortCount;
+ int32_t outputPortCount;
+} AMIDI_DeviceInfo;
+
+/*
+ * Device API
+ */
+/*
+ * Retrieves the native API device token for the specified Java API device ID.
+ *
+ * uid The Java API id of the device.
+ * devicePtr Receives the associated native API token for the device.
+ *
+ * Returns OK or a (negative) error code.
+ */
+status_t AMIDI_getDeviceById(int32_t id, AMIDI_Device *devicePtr);
+
+/*
+ * Retrieves information for the native MIDI device.
+ *
+ * device The Native API token for the device.
+ * deviceInfoPtr Receives the associated device info.
+ *
+ * Returns OK or a (negative) error code.
+ */
+status_t AMIDI_getDeviceInfo(AMIDI_Device device, AMIDI_DeviceInfo *deviceInfoPtr);
+
+/*
+ * API for receiving data from the Output port of a device.
+ */
+/*
+ * Opens the output port.
+ *
+ * device Identifies the device.
+ * portNumber Specifies the zero-based port index on the device to open.
+ * outputPortPtr Receives the native API port identifier of the opened port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_openOutputPort(AMIDI_Device device, int portNumber, AMIDI_OutputPort *outputPortPtr);
+
+/*
+ * Receives any pending MIDI messages (up to the specified maximum number of messages).
+ *
+ * outputPort Identifies the port to receive messages from.
+ * messages Points to an array (size maxMessages) to receive the MIDI messages.
+ * maxMessages The number of messages allocated in the messages array.
+ *
+ * Returns the number of messages received, or a (negative) error code.
+ */
+ssize_t AMIDI_receive(AMIDI_OutputPort outputPort, AMIDI_Message *messages, ssize_t maxMessages);
+
+/*
+ * Closes the output port.
+ *
+ * outputPort The native API port identifier of the port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_closeOutputPort(AMIDI_OutputPort outputPort);
+
+/*
+ * API for sending data to the Input port of a device.
+ */
+/*
+ * Opens the input port.
+ *
+ * device Identifies the device.
+ * portNumber Specifies the zero-based port index on the device to open.
+ * inputPortPtr Receives the native API port identifier of the opened port.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_openInputPort(AMIDI_Device device, int portNumber, AMIDI_InputPort *inputPortPtr);
+
+/*
+ * Returns the maximum number of bytes that can be received in a single MIDI message.
+ */
+ssize_t AMIDI_getMaxMessageSizeInBytes(AMIDI_InputPort inputPort);
+
+/*
+ * Sends data to the specified input port.
+ *
+ * inputPort The native API identifier of the port to send data to.
+ * buffer Points to the array of bytes containing the data to send.
+ * numBytes Specifies the number of bytes to write.
+ *
+ * Returns The number of bytes sent or a (negative) error code.
+ */
+ssize_t AMIDI_send(AMIDI_InputPort inputPort, uint8_t *buffer, ssize_t numBytes);
+
+/*
+ * Sends data to the specified input port with a timestamp.
+ *
+ * inputPort The native API identifier of the port to send data to.
+ * buffer Points to the array of bytes containing the data to send.
+ * numBytes Specifies the number of bytes to write.
+ * timestamp The time stamp to associate with the sent data.
+ *
+ * Returns The number of bytes sent or a (negative) error code.
+ */
+ssize_t AMIDI_sendWithTimestamp(AMIDI_InputPort inputPort, uint8_t *buffer,
+ ssize_t numBytes, int64_t timestamp);
+
+/*
+ * Sends a message with a 'MIDI flush command code' to the specified port.
+ *
+ * inputPort The native API identifier of the port to send the flush message to.
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_flush(AMIDI_InputPort inputPort);
+
+/*
+ * Closes the input port.
+ *
+ * inputPort The native API port identifier of the port.
+ *
+ *
+ * Returns OK, or a (negative) error code.
+ */
+status_t AMIDI_closeInputPort(AMIDI_InputPort inputPort);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ANDROID_MEDIA_MIDI_H_ */
diff --git a/media/tests/NativeMidiDemo/Android.mk b/media/tests/NativeMidiDemo/Android.mk
new file mode 100644
index 0000000..6b08f6b
--- /dev/null
+++ b/media/tests/NativeMidiDemo/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := NativeMidiDemo
+
+#LOCAL_SDK_VERSION := current
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+
+LOCAL_JNI_SHARED_LIBRARIES := libnativemidi_jni
+
+include $(BUILD_PACKAGE)
+
+# Include packages in subdirectories
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/tests/NativeMidiDemo/AndroidManifest.xml b/media/tests/NativeMidiDemo/AndroidManifest.xml
new file mode 100644
index 0000000..322873f
--- /dev/null
+++ b/media/tests/NativeMidiDemo/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.android.nativemididemo"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application
+ android:allowBackup="false"
+ android:fullBackupContent="false"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name">
+ <uses-feature android:name="android.software.midi" android:required="true"/>
+ <activity android:name=".NativeMidi"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java
new file mode 100644
index 0000000..0969b10
--- /dev/null
+++ b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/NativeMidi.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2016 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.example.android.nativemididemo;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
+import android.media.midi.MidiReceiver;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import java.io.IOException;
+
+public class NativeMidi extends Activity
+{
+ private TextView mCallbackStatusTextView;
+ private TextView mJavaMidiStatusTextView;
+ private TextView mMessagesTextView;
+ private RadioGroup mMidiDevicesRadioGroup;
+ private Handler mTimerHandler = new Handler();
+ private boolean mAudioWorks;
+ private final int mMinFramesPerBuffer = 32; // See {min|max}PlaySamples in nativemidi-jni.cpp
+ private final int mMaxFramesPerBuffer = 1000;
+ private int mFramesPerBuffer;
+
+ private TouchableScrollView mMessagesContainer;
+ private MidiManager mMidiManager;
+ private MidiOutputPortSelector mActivePortSelector;
+
+ private Runnable mTimerRunnable = new Runnable() {
+ private long mLastTime;
+ private long mLastPlaybackCounter;
+ private int mLastCallbackRate;
+ private long mLastUntouchedTime;
+
+ @Override
+ public void run() {
+ final long checkIntervalMs = 1000;
+ long currentTime = System.currentTimeMillis();
+ long currentPlaybackCounter = getPlaybackCounter();
+ if (currentTime - mLastTime >= checkIntervalMs) {
+ int callbackRate = Math.round(
+ (float)(currentPlaybackCounter - mLastPlaybackCounter) /
+ ((float)(currentTime - mLastTime) / (float)1000));
+ if (mLastCallbackRate != callbackRate) {
+ mCallbackStatusTextView.setText(
+ "CB: " + callbackRate + " Hz");
+ mLastCallbackRate = callbackRate;
+ }
+ mLastTime = currentTime;
+ mLastPlaybackCounter = currentPlaybackCounter;
+ }
+
+ String[] newMessages = getRecentMessages();
+ if (newMessages != null) {
+ for (String message : newMessages) {
+ mMessagesTextView.append(message);
+ mMessagesTextView.append("\n");
+ }
+ if (!mMessagesContainer.isTouched) {
+ if (mLastUntouchedTime == 0) mLastUntouchedTime = currentTime;
+ if (currentTime - mLastUntouchedTime > 3000) {
+ mMessagesContainer.fullScroll(View.FOCUS_DOWN);
+ }
+ } else {
+ mLastUntouchedTime = 0;
+ }
+ }
+
+ mTimerHandler.postDelayed(this, checkIntervalMs / 4);
+ }
+ };
+
+ private void addMessage(final String message) {
+ mTimerHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mMessagesTextView.append(message);
+ }
+ }, 0);
+ }
+
+ private class MidiOutputPortSelector implements View.OnClickListener {
+ private final MidiDeviceInfo mDeviceInfo;
+ private final int mPortNumber;
+ private MidiDevice mDevice;
+ private MidiOutputPort mOutputPort;
+
+ MidiOutputPortSelector() {
+ mDeviceInfo = null;
+ mPortNumber = -1;
+ }
+
+ MidiOutputPortSelector(MidiDeviceInfo info, int portNumber) {
+ mDeviceInfo = info;
+ mPortNumber = portNumber;
+ }
+
+ MidiDeviceInfo getDeviceInfo() { return mDeviceInfo; }
+
+ @Override
+ public void onClick(View v) {
+ if (mActivePortSelector != null) {
+ mActivePortSelector.close();
+ mActivePortSelector = null;
+ }
+ if (mDeviceInfo == null) {
+ mActivePortSelector = this;
+ return;
+ }
+ mMidiManager.openDevice(mDeviceInfo, new MidiManager.OnDeviceOpenedListener() {
+ @Override
+ public void onDeviceOpened(MidiDevice device) {
+ if (device == null) {
+ addMessage("! Failed to open MIDI device !\n");
+ } else {
+ mDevice = device;
+ try {
+ mDevice.mirrorToNative();
+ startReadingMidi(mDevice.getInfo().getId(), mPortNumber);
+ } catch (IOException e) {
+ addMessage("! Failed to mirror to native !\n" + e.getMessage() + "\n");
+ }
+
+ mActivePortSelector = MidiOutputPortSelector.this;
+
+ mOutputPort = device.openOutputPort(mPortNumber);
+ mOutputPort.connect(mMidiReceiver);
+ }
+ }
+ }, null);
+ }
+
+ void closePortOnly() {
+ stopReadingMidi();
+ }
+
+ void close() {
+ closePortOnly();
+ try {
+ if (mOutputPort != null) {
+ mOutputPort.close();
+ }
+ } catch (IOException e) {
+ mMessagesTextView.append("! Port close error: " + e + "\n");
+ } finally {
+ mOutputPort = null;
+ }
+ try {
+ if (mDevice != null) {
+ mDevice.close();
+ }
+ } catch (IOException e) {
+ mMessagesTextView.append("! Device close error: " + e + "\n");
+ } finally {
+ mDevice = null;
+ }
+ }
+ }
+
+ private MidiManager.DeviceCallback mMidiDeviceCallback = new MidiManager.DeviceCallback() {
+ @Override
+ public void onDeviceAdded(MidiDeviceInfo info) {
+ Bundle deviceProps = info.getProperties();
+ String deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_NAME);
+ if (deviceName == null) {
+ deviceName = deviceProps.getString(MidiDeviceInfo.PROPERTY_MANUFACTURER);
+ }
+
+ for (MidiDeviceInfo.PortInfo port : info.getPorts()) {
+ if (port.getType() != MidiDeviceInfo.PortInfo.TYPE_OUTPUT) continue;
+ String portName = port.getName();
+ int portNumber = port.getPortNumber();
+ if (portName.length() == 0) portName = "[" + portNumber + "]";
+ portName += "@" + deviceName;
+ RadioButton outputDevice = new RadioButton(NativeMidi.this);
+ outputDevice.setText(portName);
+ outputDevice.setTag(info);
+ outputDevice.setOnClickListener(new MidiOutputPortSelector(info, portNumber));
+ mMidiDevicesRadioGroup.addView(outputDevice);
+ }
+
+ NativeMidi.this.updateKeepScreenOn();
+ }
+
+ @Override
+ public void onDeviceRemoved(MidiDeviceInfo info) {
+ if (mActivePortSelector != null && info.equals(mActivePortSelector.getDeviceInfo())) {
+ mActivePortSelector.close();
+ mActivePortSelector = null;
+ }
+ int removeButtonStart = -1, removeButtonCount = 0;
+ final int buttonCount = mMidiDevicesRadioGroup.getChildCount();
+ boolean checked = false;
+ for (int i = 0; i < buttonCount; ++i) {
+ RadioButton button = (RadioButton) mMidiDevicesRadioGroup.getChildAt(i);
+ if (!info.equals(button.getTag())) continue;
+ if (removeButtonStart == -1) removeButtonStart = i;
+ ++removeButtonCount;
+ if (button.isChecked()) checked = true;
+ }
+ if (removeButtonStart != -1) {
+ mMidiDevicesRadioGroup.removeViews(removeButtonStart, removeButtonCount);
+ if (checked) {
+ mMidiDevicesRadioGroup.check(R.id.device_none);
+ }
+ }
+
+ NativeMidi.this.updateKeepScreenOn();
+ }
+ };
+
+ private class JavaMidiReceiver extends MidiReceiver implements Runnable {
+ @Override
+ public void onSend(byte[] data, int offset,
+ int count, long timestamp) throws IOException {
+ mTimerHandler.removeCallbacks(this);
+ mTimerHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mJavaMidiStatusTextView.setText("Java: MSG");
+ }
+ }, 0);
+ mTimerHandler.postDelayed(this, 100);
+ }
+
+ @Override
+ public void run() {
+ mJavaMidiStatusTextView.setText("Java: ---");
+ }
+ }
+
+ private JavaMidiReceiver mMidiReceiver = new JavaMidiReceiver();
+
+ private void updateKeepScreenOn() {
+ if (mMidiDevicesRadioGroup.getChildCount() > 1) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mCallbackStatusTextView = (TextView) findViewById(R.id.callback_status);
+ mJavaMidiStatusTextView = (TextView) findViewById(R.id.java_midi_status);
+ mMessagesTextView = (TextView) findViewById(R.id.messages);
+ mMessagesContainer = (TouchableScrollView) findViewById(R.id.messages_scroll);
+ mMidiDevicesRadioGroup = (RadioGroup) findViewById(R.id.devices);
+ RadioButton deviceNone = (RadioButton) findViewById(R.id.device_none);
+ deviceNone.setOnClickListener(new MidiOutputPortSelector());
+
+ AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+ if (sampleRate == null) sampleRate = "48000";
+ String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+ if (framesPerBuffer == null) framesPerBuffer = Integer.toString(mMaxFramesPerBuffer);
+ mFramesPerBuffer = Integer.parseInt(framesPerBuffer);
+ String audioInitResult = initAudio(Integer.parseInt(sampleRate), mFramesPerBuffer);
+ mMessagesTextView.append("Open SL ES init: " + audioInitResult + "\n");
+
+ if (audioInitResult.startsWith("Success")) {
+ mAudioWorks = true;
+ mTimerHandler.postDelayed(mTimerRunnable, 0);
+ mTimerHandler.postDelayed(mMidiReceiver, 0);
+ }
+
+ mMidiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
+ mMidiManager.registerDeviceCallback(mMidiDeviceCallback, new Handler());
+ for (MidiDeviceInfo info : mMidiManager.getDevices()) {
+ mMidiDeviceCallback.onDeviceAdded(info);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mAudioWorks) {
+ mTimerHandler.removeCallbacks(mTimerRunnable);
+ pauseAudio();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (mAudioWorks) {
+ mTimerHandler.postDelayed(mTimerRunnable, 0);
+ resumeAudio();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mActivePortSelector != null) {
+ mActivePortSelector.close();
+ mActivePortSelector = null;
+ }
+ shutdownAudio();
+ super.onDestroy();
+ }
+
+ public void onClearMessages(View v) {
+ mMessagesTextView.setText("");
+ }
+
+ public void onClosePort(View v) {
+ if (mActivePortSelector != null) {
+ mActivePortSelector.closePortOnly();
+ }
+ }
+
+ private native String initAudio(int sampleRate, int playSamples);
+ private native void pauseAudio();
+ private native void resumeAudio();
+ private native void shutdownAudio();
+
+ private native long getPlaybackCounter();
+ private native String[] getRecentMessages();
+
+ private native void startReadingMidi(int deviceId, int portNumber);
+ private native void stopReadingMidi();
+
+ static {
+ System.loadLibrary("nativemidi_jni");
+ }
+}
diff --git a/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java
new file mode 100644
index 0000000..645aafa
--- /dev/null
+++ b/media/tests/NativeMidiDemo/java/com/example/android/nativemididemo/TouchableScrollView.java
@@ -0,0 +1,32 @@
+package com.example.android.nativemididemo;
+
+import android.content.Context;
+import android.view.MotionEvent;
+import android.util.AttributeSet;
+import android.widget.ScrollView;
+
+public class TouchableScrollView extends ScrollView {
+ public boolean isTouched;
+
+ public TouchableScrollView(Context context) {
+ super(context);
+ }
+
+ public TouchableScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ isTouched = true;
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ isTouched = false;
+ break;
+ }
+ return super.onTouchEvent(event);
+ }
+}
diff --git a/media/tests/NativeMidiDemo/jni/Android.mk b/media/tests/NativeMidiDemo/jni/Android.mk
new file mode 100644
index 0000000..69a64bd
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := libnativemidi_jni
+
+LOCAL_SRC_FILES := \
+ nativemidi-jni.cpp \
+ messagequeue.cpp
+
+LOCAL_CFLAGS += -Wall -Wextra -Werror -O0
+
+LOCAL_C_INCLUDES += \
+ frameworks/base/media/native
+
+LOCAL_CXX_STL := libc++_static
+
+LOCAL_SHARED_LIBRARIES := libOpenSLES libmidi
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/media/tests/NativeMidiDemo/jni/messagequeue.cpp b/media/tests/NativeMidiDemo/jni/messagequeue.cpp
new file mode 100644
index 0000000..ffaef38
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/messagequeue.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <atomic>
+#include <stdio.h>
+#include <string.h>
+
+#include "messagequeue.h"
+
+namespace nativemididemo {
+
+static const int messageBufferSize = 64 * 1024;
+static char messageBuffer[messageBufferSize];
+static std::atomic_ullong messagesLastWritePosition;
+
+void writeMessage(const char* message)
+{
+ static unsigned long long lastWritePos = 0;
+ size_t messageLen = strlen(message);
+ if (messageLen == 0) return;
+
+ messageLen += 1; // Also count in the null terminator.
+ char buffer[1024];
+ if (messageLen >= messageBufferSize) {
+ snprintf(buffer, sizeof(buffer), "!!! Message too long: %zu bytes !!!", messageLen);
+ message = buffer;
+ messageLen = strlen(message);
+ }
+
+ size_t wrappedWritePos = lastWritePos % messageBufferSize;
+ if (wrappedWritePos + messageLen >= messageBufferSize) {
+ size_t tailLen = messageBufferSize - wrappedWritePos;
+ memset(messageBuffer + wrappedWritePos, 0, tailLen);
+ lastWritePos += tailLen;
+ wrappedWritePos = 0;
+ }
+
+ memcpy(messageBuffer + wrappedWritePos, message, messageLen);
+ lastWritePos += messageLen;
+ messagesLastWritePosition.store(lastWritePos);
+}
+
+static char messageBufferCopy[messageBufferSize];
+
+jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject)
+{
+ static unsigned long long lastReadPos = 0;
+ const char* overrunMessage = "";
+ size_t messagesCount = 0;
+ jobjectArray result = NULL;
+
+ // First we copy the portion of the message buffer into messageBufferCopy. If after finishing
+ // the copy we notice that the writer has mutated the portion of the buffer that we were
+ // copying, we report an overrun. Afterwards we can safely read messages from the copy.
+ memset(messageBufferCopy, 0, sizeof(messageBufferCopy));
+ unsigned long long lastWritePos = messagesLastWritePosition.load();
+ if (lastWritePos - lastReadPos > messageBufferSize) {
+ overrunMessage = "!!! Message buffer overrun !!!";
+ messagesCount = 1;
+ lastReadPos = lastWritePos;
+ goto create_array;
+ }
+ if (lastWritePos == lastReadPos) return result;
+ if (lastWritePos / messageBufferSize == lastReadPos / messageBufferSize) {
+ size_t wrappedReadPos = lastReadPos % messageBufferSize;
+ memcpy(messageBufferCopy + wrappedReadPos,
+ messageBuffer + wrappedReadPos,
+ lastWritePos % messageBufferSize - wrappedReadPos);
+ } else {
+ size_t wrappedReadPos = lastReadPos % messageBufferSize;
+ memcpy(messageBufferCopy, messageBuffer, lastWritePos % messageBufferSize);
+ memcpy(messageBufferCopy + wrappedReadPos,
+ messageBuffer + wrappedReadPos,
+ messageBufferSize - wrappedReadPos);
+ }
+ {
+ unsigned long long newLastWritePos = messagesLastWritePosition.load();
+ if (newLastWritePos - lastReadPos > messageBufferSize) {
+ overrunMessage = "!!! Message buffer overrun !!!";
+ messagesCount = 1;
+ lastReadPos = lastWritePos = newLastWritePos;
+ goto create_array;
+ }
+ }
+ // Otherwise we ignore newLastWritePos, since we only have a copy of the buffer
+ // up to lastWritePos.
+
+ for (unsigned long long readPos = lastReadPos; readPos < lastWritePos; ) {
+ size_t messageLen = strlen(messageBufferCopy + (readPos % messageBufferSize));
+ if (messageLen != 0) {
+ readPos += messageLen + 1;
+ messagesCount++;
+ } else {
+ // Skip to the beginning of the buffer.
+ readPos = (readPos / messageBufferSize + 1) * messageBufferSize;
+ }
+ }
+ if (messagesCount == 0) {
+ lastReadPos = lastWritePos;
+ return result;
+ }
+
+create_array:
+ result = env->NewObjectArray(
+ messagesCount, env->FindClass("java/lang/String"), env->NewStringUTF(overrunMessage));
+ if (lastWritePos == lastReadPos) return result;
+
+ jsize arrayIndex = 0;
+ while (lastReadPos < lastWritePos) {
+ size_t wrappedReadPos = lastReadPos % messageBufferSize;
+ if (messageBufferCopy[wrappedReadPos] != '\0') {
+ jstring message = env->NewStringUTF(messageBufferCopy + wrappedReadPos);
+ env->SetObjectArrayElement(result, arrayIndex++, message);
+ lastReadPos += env->GetStringLength(message) + 1;
+ env->DeleteLocalRef(message);
+ } else {
+ // Skip to the beginning of the buffer.
+ lastReadPos = (lastReadPos / messageBufferSize + 1) * messageBufferSize;
+ }
+ }
+ return result;
+}
+
+} // namespace nativemididemo
diff --git a/media/tests/NativeMidiDemo/jni/messagequeue.h b/media/tests/NativeMidiDemo/jni/messagequeue.h
new file mode 100644
index 0000000..20aa9e8
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/messagequeue.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#ifndef NATIVEMIDIDEMO_MESSAGEQUEUE_H
+#define NATIVEMIDIDEMO_MESSAGEQUEUE_H
+
+#include <jni.h>
+
+namespace nativemididemo {
+
+void writeMessage(const char* message);
+jobjectArray getRecentMessagesForJava(JNIEnv* env, jobject thiz);
+
+} // namespace nativemididemo
+
+#endif // NATIVEMIDIDEMO_MESSAGEQUEUE_H
diff --git a/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp b/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp
new file mode 100644
index 0000000..8aa874a
--- /dev/null
+++ b/media/tests/NativeMidiDemo/jni/nativemidi-jni.cpp
@@ -0,0 +1,285 @@
+#include <atomic>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <jni.h>
+
+#include <midi/midi.h>
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+#include "messagequeue.h"
+
+extern "C" {
+JNIEXPORT jstring JNICALL Java_com_example_android_nativemididemo_NativeMidi_initAudio(
+ JNIEnv* env, jobject thiz, jint sampleRate, jint playSamples);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT jlong JNICALL Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT jobjectArray JNICALL Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
+ JNIEnv* env, jobject thiz);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
+ JNIEnv* env, jobject thiz, jint deviceId, jint portNumber);
+JNIEXPORT void JNICALL Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
+ JNIEnv* env, jobject thiz);
+}
+
+static const char* errStrings[] = {
+ "SL_RESULT_SUCCESS", // 0
+ "SL_RESULT_PRECONDITIONS_VIOLATED", // 1
+ "SL_RESULT_PARAMETER_INVALID", // 2
+ "SL_RESULT_MEMORY_FAILURE", // 3
+ "SL_RESULT_RESOURCE_ERROR", // 4
+ "SL_RESULT_RESOURCE_LOST", // 5
+ "SL_RESULT_IO_ERROR", // 6
+ "SL_RESULT_BUFFER_INSUFFICIENT", // 7
+ "SL_RESULT_CONTENT_CORRUPTED", // 8
+ "SL_RESULT_CONTENT_UNSUPPORTED", // 9
+ "SL_RESULT_CONTENT_NOT_FOUND", // 10
+ "SL_RESULT_PERMISSION_DENIED", // 11
+ "SL_RESULT_FEATURE_UNSUPPORTED", // 12
+ "SL_RESULT_INTERNAL_ERROR", // 13
+ "SL_RESULT_UNKNOWN_ERROR", // 14
+ "SL_RESULT_OPERATION_ABORTED", // 15
+ "SL_RESULT_CONTROL_LOST" }; // 16
+static const char* getSLErrStr(int code) {
+ return errStrings[code];
+}
+
+static SLObjectItf engineObject;
+static SLEngineItf engineEngine;
+static SLObjectItf outputMixObject;
+static SLObjectItf playerObject;
+static SLPlayItf playerPlay;
+static SLAndroidSimpleBufferQueueItf playerBufferQueue;
+
+static const int minPlaySamples = 32;
+static const int maxPlaySamples = 1000;
+static std::atomic_int playSamples(maxPlaySamples);
+static short playBuffer[maxPlaySamples];
+
+static std::atomic_ullong sharedCounter;
+
+static AMIDI_Device midiDevice = AMIDI_INVALID_HANDLE;
+static std::atomic<AMIDI_OutputPort> midiOutputPort(AMIDI_INVALID_HANDLE);
+
+static int setPlaySamples(int newPlaySamples)
+{
+ if (newPlaySamples < minPlaySamples) newPlaySamples = minPlaySamples;
+ if (newPlaySamples > maxPlaySamples) newPlaySamples = maxPlaySamples;
+ playSamples.store(newPlaySamples);
+ return newPlaySamples;
+}
+
+// Amount of messages we are ready to handle during one callback cycle.
+static const size_t MAX_INCOMING_MIDI_MESSAGES = 20;
+// Static allocation to save time in the callback.
+static AMIDI_Message incomingMidiMessages[MAX_INCOMING_MIDI_MESSAGES];
+
+static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void */*context*/)
+{
+ sharedCounter++;
+
+ AMIDI_OutputPort outputPort = midiOutputPort.load();
+ if (outputPort != AMIDI_INVALID_HANDLE) {
+ char midiDumpBuffer[1024];
+ ssize_t midiReceived = AMIDI_receive(
+ outputPort, incomingMidiMessages, MAX_INCOMING_MIDI_MESSAGES);
+ if (midiReceived >= 0) {
+ for (ssize_t i = 0; i < midiReceived; ++i) {
+ AMIDI_Message* msg = &incomingMidiMessages[i];
+ if (msg->opcode == AMIDI_OPCODE_DATA) {
+ memset(midiDumpBuffer, 0, sizeof(midiDumpBuffer));
+ int pos = snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
+ "%" PRIx64 " ", msg->timestamp);
+ for (uint8_t *b = msg->buffer, *e = b + msg->len; b < e; ++b) {
+ pos += snprintf(midiDumpBuffer + pos, sizeof(midiDumpBuffer) - pos,
+ "%02x ", *b);
+ }
+ nativemididemo::writeMessage(midiDumpBuffer);
+ } else if (msg->opcode == AMIDI_OPCODE_FLUSH) {
+ nativemididemo::writeMessage("MIDI flush");
+ }
+ }
+ } else {
+ snprintf(midiDumpBuffer, sizeof(midiDumpBuffer),
+ "! MIDI Receive error: %s !", strerror(-midiReceived));
+ nativemididemo::writeMessage(midiDumpBuffer);
+ }
+ }
+
+ size_t usedBufferSize = playSamples.load() * sizeof(playBuffer[0]);
+ if (usedBufferSize > sizeof(playBuffer)) {
+ usedBufferSize = sizeof(playBuffer);
+ }
+ (*bq)->Enqueue(bq, playBuffer, usedBufferSize);
+}
+
+jstring Java_com_example_android_nativemididemo_NativeMidi_initAudio(
+ JNIEnv* env, jobject, jint sampleRate, jint playSamples) {
+ const char* stage;
+ SLresult result;
+ char printBuffer[1024];
+
+ playSamples = setPlaySamples(playSamples);
+
+ result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
+ if (SL_RESULT_SUCCESS != result) { stage = "slCreateEngine"; goto handle_error; }
+
+ result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != result) { stage = "realize Engine object"; goto handle_error; }
+
+ result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
+ if (SL_RESULT_SUCCESS != result) { stage = "get Engine interface"; goto handle_error; }
+
+ result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL);
+ if (SL_RESULT_SUCCESS != result) { stage = "CreateOutputMix"; goto handle_error; }
+
+ result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != result) { stage = "realize OutputMix object"; goto handle_error; }
+
+ {
+ SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 1, (SLuint32)sampleRate * 1000,
+ SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+ SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN };
+ SLDataLocator_AndroidSimpleBufferQueue loc_bufq =
+ { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
+ SLDataSource audioSrc = { &loc_bufq, &format_pcm };
+ SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject };
+ SLDataSink audioSnk = { &loc_outmix, NULL };
+ const SLInterfaceID ids[1] = { SL_IID_BUFFERQUEUE };
+ const SLboolean req[1] = { SL_BOOLEAN_TRUE };
+ result = (*engineEngine)->CreateAudioPlayer(
+ engineEngine, &playerObject, &audioSrc, &audioSnk, 1, ids, req);
+ if (SL_RESULT_SUCCESS != result) { stage = "CreateAudioPlayer"; goto handle_error; }
+
+ result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
+ if (SL_RESULT_SUCCESS != result) { stage = "realize Player object"; goto handle_error; }
+ }
+
+ result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
+ if (SL_RESULT_SUCCESS != result) { stage = "get Play interface"; goto handle_error; }
+
+ result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueue);
+ if (SL_RESULT_SUCCESS != result) { stage = "get BufferQueue interface"; goto handle_error; }
+
+ result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, bqPlayerCallback, NULL);
+ if (SL_RESULT_SUCCESS != result) { stage = "register BufferQueue callback"; goto handle_error; }
+
+ result = (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
+ if (SL_RESULT_SUCCESS != result) {
+ stage = "enqueue into PlayerBufferQueue"; goto handle_error; }
+
+ result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
+ if (SL_RESULT_SUCCESS != result) {
+ stage = "SetPlayState(SL_PLAYSTATE_PLAYING)"; goto handle_error; }
+
+ snprintf(printBuffer, sizeof(printBuffer),
+ "Success, sample rate %d, buffer samples %d", sampleRate, playSamples);
+ return env->NewStringUTF(printBuffer);
+
+handle_error:
+ snprintf(printBuffer, sizeof(printBuffer), "Error at %s: %s", stage, getSLErrStr(result));
+ return env->NewStringUTF(printBuffer);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_pauseAudio(
+ JNIEnv*, jobject) {
+ if (playerPlay != NULL) {
+ (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PAUSED);
+ }
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_resumeAudio(
+ JNIEnv*, jobject) {
+ if (playerBufferQueue != NULL && playerPlay != NULL) {
+ (*playerBufferQueue)->Enqueue(playerBufferQueue, playBuffer, sizeof(playBuffer));
+ (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
+ }
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_shutdownAudio(
+ JNIEnv*, jobject) {
+ if (playerObject != NULL) {
+ (*playerObject)->Destroy(playerObject);
+ playerObject = NULL;
+ playerPlay = NULL;
+ playerBufferQueue = NULL;
+ }
+
+ if (outputMixObject != NULL) {
+ (*outputMixObject)->Destroy(outputMixObject);
+ outputMixObject = NULL;
+ }
+
+ if (engineObject != NULL) {
+ (*engineObject)->Destroy(engineObject);
+ engineObject = NULL;
+ engineEngine = NULL;
+ }
+}
+
+jlong Java_com_example_android_nativemididemo_NativeMidi_getPlaybackCounter(JNIEnv*, jobject) {
+ return sharedCounter.load();
+}
+
+jobjectArray Java_com_example_android_nativemididemo_NativeMidi_getRecentMessages(
+ JNIEnv* env, jobject thiz) {
+ return nativemididemo::getRecentMessagesForJava(env, thiz);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_startReadingMidi(
+ JNIEnv*, jobject, jint deviceId, jint portNumber) {
+ char buffer[1024];
+
+ int result = AMIDI_getDeviceById(deviceId, &midiDevice);
+ if (result == 0) {
+ snprintf(buffer, sizeof(buffer), "Obtained device token for uid %d: token %d", deviceId, midiDevice);
+ } else {
+ snprintf(buffer, sizeof(buffer), "Could not obtain device token for uid %d: %d", deviceId, result);
+ }
+ nativemididemo::writeMessage(buffer);
+ if (result) return;
+
+ AMIDI_DeviceInfo deviceInfo;
+ result = AMIDI_getDeviceInfo(midiDevice, &deviceInfo);
+ if (result == 0) {
+ snprintf(buffer, sizeof(buffer), "Device info: uid %d, type %d, priv %d, ports %d I / %d O",
+ deviceInfo.uid, deviceInfo.type, deviceInfo.isPrivate,
+ (int)deviceInfo.inputPortCount, (int)deviceInfo.outputPortCount);
+ } else {
+ snprintf(buffer, sizeof(buffer), "Could not obtain device info %d", result);
+ }
+ nativemididemo::writeMessage(buffer);
+ if (result) return;
+
+ AMIDI_OutputPort outputPort;
+ result = AMIDI_openOutputPort(midiDevice, portNumber, &outputPort);
+ if (result == 0) {
+ snprintf(buffer, sizeof(buffer), "Opened port %d: token %d", portNumber, outputPort);
+ midiOutputPort.store(outputPort);
+ } else {
+ snprintf(buffer, sizeof(buffer), "Could not open port %d: %d", deviceId, result);
+ }
+ nativemididemo::writeMessage(buffer);
+}
+
+void Java_com_example_android_nativemididemo_NativeMidi_stopReadingMidi(
+ JNIEnv*, jobject) {
+ AMIDI_OutputPort outputPort = midiOutputPort.exchange(AMIDI_INVALID_HANDLE);
+ if (outputPort == AMIDI_INVALID_HANDLE) return;
+ int result = AMIDI_closeOutputPort(outputPort);
+ char buffer[1024];
+ if (result == 0) {
+ snprintf(buffer, sizeof(buffer), "Closed port by token %d", outputPort);
+ } else {
+ snprintf(buffer, sizeof(buffer), "Could not close port by token %d: %d", outputPort, result);
+ }
+ nativemididemo::writeMessage(buffer);
+}
diff --git a/media/tests/NativeMidiDemo/res/layout/main.xml b/media/tests/NativeMidiDemo/res/layout/main.xml
new file mode 100644
index 0000000..465d471
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/layout/main.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+ <TextView
+ android:id="@+id/callback_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ android:text=" | "
+ />
+ <TextView
+ android:id="@+id/java_midi_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ />
+ <TextView
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ android:text=" "
+ />
+ </LinearLayout>
+ <HorizontalScrollView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="6sp"
+ android:paddingBottom="6sp"
+ >
+ <RadioGroup
+ android:id="@+id/devices"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:checkedButton="@+id/device_none"
+ >
+ <RadioButton
+ android:id="@+id/device_none"
+ android:text="None"
+ />
+ </RadioGroup>
+ </HorizontalScrollView>
+ <com.example.android.nativemididemo.TouchableScrollView android:id="@+id/messages_scroll"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ >
+ <TextView android:id="@+id/messages"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:typeface="monospace"
+ />
+ </com.example.android.nativemididemo.TouchableScrollView>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+ <Button android:id="@+id/clear_messages"
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/clear_messages"
+ android:onClick="onClearMessages"
+ />
+ <Button android:id="@+id/close_port"
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/close_port"
+ android:onClick="onClosePort"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png b/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/media/tests/NativeMidiDemo/res/values/strings.xml b/media/tests/NativeMidiDemo/res/values/strings.xml
new file mode 100644
index 0000000..5b69b52
--- /dev/null
+++ b/media/tests/NativeMidiDemo/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">NativeMidiDemo</string>
+ <string name="clear_messages">Clear Messages</string>
+ <string name="close_port">Close Port</string>
+</resources>
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 3ede2ee..1b1f28c 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -139,16 +139,27 @@
ANativeActivity_setWindowFlags;
ANativeActivity_setWindowFormat;
ANativeActivity_showSoftInput;
- ANativeWindow_acquire; # removed=26
- ANativeWindow_fromSurface; # removed=26
- ANativeWindow_fromSurfaceTexture; # removed=26
- ANativeWindow_getFormat; # removed=26
- ANativeWindow_getHeight; # removed=26
- ANativeWindow_getWidth; # removed=26
- ANativeWindow_lock; # removed=26
- ANativeWindow_release; # removed=26
- ANativeWindow_setBuffersGeometry; # removed=26
- ANativeWindow_unlockAndPost; # removed=26
+ AHardwareBuffer_acquire; # introduced=26
+ AHardwareBuffer_allocate; # introduced=26
+ AHardwareBuffer_describe; # introduced=26
+ AHardwareBuffer_fromHardwareBuffer; # introduced=26
+ AHardwareBuffer_getNativeHandle; # introduced=26
+ AHardwareBuffer_lock; # introduced=26
+ AHardwareBuffer_recvHandleFromUnixSocket; # introduced=26
+ AHardwareBuffer_release; # introduced=26
+ AHardwareBuffer_sendHandleToUnixSocket; # introduced=26
+ AHardwareBuffer_toHardwareBuffer; # introduced=26
+ AHardwareBuffer_unlock; # introduced=26
+ ANativeWindow_acquire;
+ ANativeWindow_fromSurface;
+ ANativeWindow_fromSurfaceTexture; # introduced-arm=13 introduced-mips=13 introduced-x86=13
+ ANativeWindow_getFormat;
+ ANativeWindow_getHeight;
+ ANativeWindow_getWidth;
+ ANativeWindow_lock;
+ ANativeWindow_release;
+ ANativeWindow_setBuffersGeometry;
+ ANativeWindow_unlockAndPost;
AObbInfo_delete;
AObbInfo_getFlags;
AObbInfo_getPackageName;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
index d4623d6..82da9a3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/InterestingConfigChanges.java
@@ -31,7 +31,8 @@
public InterestingConfigChanges(int extraFlags) {
mFlags = extraFlags | ActivityInfo.CONFIG_LOCALE
- | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT
+ | ActivityInfo.CONFIG_ASSETS_PATHS;
}
public boolean applyNewConfig(Resources res) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
new file mode 100644
index 0000000..ec8e956
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/PermissionsSummaryHelper.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 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.settingslib.applications;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.permission.RuntimePermissionPresentationInfo;
+import android.content.pm.permission.RuntimePermissionPresenter;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class PermissionsSummaryHelper {
+
+ public static void getPermissionSummary(Context context, String pkg,
+ final PermissionsResultCallback callback) {
+ final RuntimePermissionPresenter presenter =
+ RuntimePermissionPresenter.getInstance(context);
+ presenter.getAppPermissions(pkg, new RuntimePermissionPresenter.OnResultCallback() {
+ @Override
+ public void onGetAppPermissions(
+ @NonNull List<RuntimePermissionPresentationInfo> permissions) {
+ final int permissionCount = permissions.size();
+
+ int grantedStandardCount = 0;
+ int grantedAdditionalCount = 0;
+ int requestedCount = 0;
+ List<CharSequence> grantedStandardLabels = new ArrayList<>();
+
+ for (int i = 0; i < permissionCount; i++) {
+ RuntimePermissionPresentationInfo permission = permissions.get(i);
+ requestedCount++;
+ if (permission.isGranted()) {
+ if (permission.isStandard()) {
+ grantedStandardLabels.add(permission.getLabel());
+ grantedStandardCount++;
+ } else {
+ grantedAdditionalCount++;
+ }
+ }
+ }
+
+ Collator collator = Collator.getInstance();
+ collator.setStrength(Collator.PRIMARY);
+ Collections.sort(grantedStandardLabels, collator);
+
+ callback.onPermissionSummaryResult(grantedStandardCount, requestedCount,
+ grantedAdditionalCount, grantedStandardLabels);
+ }
+ }, null);
+ }
+
+ public static abstract class PermissionsResultCallback {
+ public void onAppWithPermissionsCountsResult(int standardGrantedPermissionAppCount,
+ int standardUsedPermissionAppCount) {
+ /* do nothing - stub */
+ }
+
+ public void onPermissionSummaryResult(int standardGrantedPermissionCount,
+ int requestedPermissionCount, int additionalGrantedPermissionCount,
+ List<CharSequence> grantedGroupLabels) {
+ /* do nothing - stub */
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemBars.java b/packages/SystemUI/src/com/android/systemui/SystemBars.java
index 6623cabe..b5093b3 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemBars.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemBars.java
@@ -43,13 +43,6 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- if (mStatusBar != null) {
- mStatusBar.onConfigurationChanged(newConfig);
- }
- }
-
- @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mStatusBar != null) {
mStatusBar.dump(fd, pw, args);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index ae402ef..e7256d1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -71,8 +71,15 @@
}
@Override
+ public void onPinnedStackAnimationStarted() {
+ // Disable touches while the animation is running
+ mTouchHandler.setTouchEnabled(false);
+ }
+
+ @Override
public void onPinnedStackAnimationEnded() {
- // TODO(winsonc): Disable touch interaction with the PiP until the animation ends
+ // Re-enable touches after the animation completes
+ mTouchHandler.setTouchEnabled(true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index d832810..4100b66 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -181,6 +181,10 @@
registerInputConsumer();
}
+ public void setTouchEnabled(boolean enabled) {
+ mTouchState.setAllowTouches(enabled);
+ }
+
public void onActivityPinned() {
// Reset some states once we are pinned
if (mIsTappingThrough) {
@@ -294,6 +298,7 @@
// Fall through to clean up
}
case MotionEvent.ACTION_CANCEL: {
+ mTouchState.reset();
break;
}
}
@@ -418,6 +423,10 @@
@Override
public void onDown(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return;
+ }
+
if (ENABLE_DRAG_TO_DISMISS) {
mDismissViewController.createDismissTarget();
mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY);
@@ -426,6 +435,10 @@
@Override
boolean onMove(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
if (touchState.startedDragging()) {
mSavedSnapFraction = -1f;
}
@@ -458,6 +471,10 @@
@Override
public boolean onUp(PipTouchState touchState) {
+ if (!touchState.isUserInteracting()) {
+ return false;
+ }
+
try {
if (ENABLE_DRAG_TO_DISMISS) {
mHandler.removeCallbacks(mShowDismissAffordance);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 702ad0a..a317dc3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -37,6 +37,7 @@
private final PointF mLastTouch = new PointF();
private final PointF mLastDelta = new PointF();
private final PointF mVelocity = new PointF();
+ private boolean mAllowTouches = true;
private boolean mIsUserInteracting = false;
private boolean mIsDragging = false;
private boolean mStartedDragging = false;
@@ -48,23 +49,41 @@
}
/**
+ * Resets this state.
+ */
+ public void reset() {
+ mAllowDraggingOffscreen = false;
+ mIsDragging = false;
+ mStartedDragging = false;
+ mIsUserInteracting = false;
+ }
+
+ /**
* Processess a given touch event and updates the state.
*/
public void onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
+ if (!mAllowTouches) {
+ return;
+ }
+
// Initialize the velocity tracker
initOrResetVelocityTracker();
+
mActivePointerId = ev.getPointerId(0);
mLastTouch.set(ev.getX(), ev.getY());
mDownTouch.set(mLastTouch);
- mIsDragging = false;
- mStartedDragging = false;
mAllowDraggingOffscreen = true;
mIsUserInteracting = true;
break;
}
case MotionEvent.ACTION_MOVE: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
int pointerIndex = ev.findPointerIndex(mActivePointerId);
@@ -86,6 +105,11 @@
break;
}
case MotionEvent.ACTION_POINTER_UP: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
@@ -100,6 +124,11 @@
break;
}
case MotionEvent.ACTION_UP: {
+ // Skip event if we did not start processing this touch gesture
+ if (!mIsUserInteracting) {
+ break;
+ }
+
// Update the velocity tracker
mVelocityTracker.addMovement(ev);
mVelocityTracker.computeCurrentVelocity(1000,
@@ -112,7 +141,6 @@
// Fall through to clean up
}
case MotionEvent.ACTION_CANCEL: {
- mIsUserInteracting = false;
recycleVelocityTracker();
break;
}
@@ -171,6 +199,19 @@
}
/**
+ * Sets whether touching is currently allowed.
+ */
+ public void setAllowTouches(boolean allowTouches) {
+ mAllowTouches = allowTouches;
+
+ // If the user happens to touch down before this is sent from the system during a transition
+ // then block any additional handling by resetting the state now
+ if (mIsUserInteracting) {
+ reset();
+ }
+ }
+
+ /**
* Disallows dragging offscreen for the duration of the current gesture.
*/
public void setDisallowDraggingOffscreen() {
@@ -202,6 +243,7 @@
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches);
pw.println(innerPrefix + "mDownTouch=" + mDownTouch);
pw.println(innerPrefix + "mDownDelta=" + mDownDelta);
pw.println(innerPrefix + "mLastTouch=" + mLastTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index c02067e..eb748af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -58,7 +58,7 @@
protected final Host mHost;
protected final Context mContext;
- protected final H mHandler;
+ protected final H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
private final ArraySet<Object> mListeners = new ArraySet<>();
@@ -86,7 +86,6 @@
protected QSTile(Host host) {
mHost = host;
mContext = host.getContext();
- mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
}
/**
@@ -170,7 +169,7 @@
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
}
- public final void refreshState() {
+ public void refreshState() {
refreshState(null);
}
@@ -178,7 +177,7 @@
mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
}
- public final void clearState() {
+ public void clearState() {
mHandler.sendEmptyMessage(H.CLEAR_STATE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 428fe9b..7d13f76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -84,7 +84,6 @@
mLabel = (TextView) mLabelContainer.findViewById(R.id.tile_label);
mPadLock = (ImageView) mLabelContainer.findViewById(R.id.restricted_padlock);
- mLabelContainer.setBackground(newTileBackground());
addView(mLabelContainer);
}
@@ -102,6 +101,11 @@
mLabel.setText(state.label);
}
mDivider.setVisibility(state.dualTarget ? View.VISIBLE : View.INVISIBLE);
+ if (state.dualTarget != mLabelContainer.isClickable()) {
+ mLabelContainer.setClickable(state.dualTarget);
+ mLabelContainer.setLongClickable(state.dualTarget);
+ mLabelContainer.setBackground(state.dualTarget ? newTileBackground() : null);
+ }
mLabel.setEnabled(!state.disabledByPolicy);
mPadLock.setVisibility(state.disabledByPolicy ? View.VISIBLE : View.GONE);
}
@@ -110,7 +114,9 @@
public void init(OnClickListener click, OnClickListener secondaryClick,
OnLongClickListener longClick) {
super.init(click, secondaryClick, longClick);
- mLabelContainer.setClickable(true);
mLabelContainer.setOnClickListener(secondaryClick);
+ mLabelContainer.setOnLongClickListener(longClick);
+ mLabelContainer.setClickable(false);
+ mLabelContainer.setLongClickable(false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 730b55d..98ec6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -20,6 +20,8 @@
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.Looper;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -69,6 +71,7 @@
private boolean mCustomizing;
private NotificationsQuickSettingsContainer mNotifQsContainer;
private QS mQs;
+ private boolean mFinishedFetchingTiles = false;
public QSCustomizer(Context context, AttributeSet attrs) {
super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
@@ -136,7 +139,7 @@
setTileSpecs();
setVisibility(View.VISIBLE);
mClipper.animateCircularClip(x, y, true, mExpandAnimationListener);
- new TileQueryHelper(mContext, mHost).setListener(mTileAdapter);
+ queryTiles();
mNotifQsContainer.setCustomizerAnimating(true);
mNotifQsContainer.setCustomizerShowing(true);
announceForAccessibility(mContext.getString(
@@ -145,6 +148,15 @@
}
}
+ private void queryTiles() {
+ mFinishedFetchingTiles = false;
+ Runnable tileQueryFetchCompletion = () -> {
+ Handler mainHandler = new Handler(Looper.getMainLooper());
+ mainHandler.post(() -> mFinishedFetchingTiles = true);
+ };
+ new TileQueryHelper(mContext, mHost, mTileAdapter, tileQueryFetchCompletion);
+ }
+
public void hide(int x, int y) {
if (isShown) {
MetricsLogger.hidden(getContext(), MetricsProto.MetricsEvent.QS_EDIT);
@@ -204,7 +216,9 @@
}
private void save() {
- mTileAdapter.saveSpecs(mHost);
+ if (mFinishedFetchingTiles) {
+ mTileAdapter.saveSpecs(mHost);
+ }
}
private final Callback mKeyguardCallback = () -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 72e6fcc..386294a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -24,7 +24,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.service.quicksettings.TileService;
@@ -49,22 +48,36 @@
private final ArrayList<TileInfo> mTiles = new ArrayList<>();
private final ArrayList<String> mSpecs = new ArrayList<>();
private final Context mContext;
- private TileStateListener mListener;
+ private final TileStateListener mListener;
+ private final QSTileHost mHost;
+ private final Runnable mCompletion;
- public TileQueryHelper(Context context, QSTileHost host) {
+ public TileQueryHelper(Context context, QSTileHost host,
+ TileStateListener listener, Runnable completion) {
mContext = context;
- addSystemTiles(host);
+ mListener = listener;
+ mHost = host;
+ mCompletion = completion;
+ addSystemTiles();
// TODO: Live?
}
- private void addSystemTiles(final QSTileHost host) {
- String possible = mContext.getString(R.string.quick_settings_tiles_stock);
- String[] possibleTiles = possible.split(",");
+ private void addSystemTiles() {
+ // Enqueue jobs to fetch every system tile and then ever package tile.
final Handler qsHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER));
final Handler mainHandler = new Handler(Looper.getMainLooper());
+ addStockTiles(mainHandler, qsHandler);
+ addPackageTiles(mainHandler, qsHandler);
+ // Then enqueue the completion. It should always be last
+ qsHandler.post(mCompletion);
+ }
+
+ private void addStockTiles(Handler mainHandler, Handler bgHandler) {
+ String possible = mContext.getString(R.string.quick_settings_tiles_stock);
+ String[] possibleTiles = possible.split(",");
for (int i = 0; i < possibleTiles.length; i++) {
final String spec = possibleTiles[i];
- final QSTile<?> tile = host.createTile(spec);
+ final QSTile<?> tile = mHost.createTile(spec);
if (tile == null) {
continue;
} else if (!tile.isAvailable()) {
@@ -75,7 +88,7 @@
tile.clearState();
tile.refreshState();
tile.setListening(this, false);
- qsHandler.post(new Runnable() {
+ bgHandler.post(new Runnable() {
@Override
public void run() {
final QSTile.State state = tile.newTileState();
@@ -93,21 +106,60 @@
}
});
}
- qsHandler.post(new Runnable() {
- @Override
- public void run() {
- mainHandler.post(new Runnable() {
- @Override
- public void run() {
- new QueryTilesTask().execute(host.getTiles());
- }
- });
+ }
+
+ private void addPackageTiles(Handler mainHandler, Handler bgHandler) {
+ bgHandler.post(() -> {
+ Collection<QSTile<?>> params = mHost.getTiles();
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+ new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
+ String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
+
+ for (ResolveInfo info : services) {
+ String packageName = info.serviceInfo.packageName;
+ ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
+
+ // Don't include apps that are a part of the default tile set.
+ if (stockTiles.contains(componentName.flattenToString())) {
+ continue;
+ }
+
+ final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
+ String spec = CustomTile.toSpec(componentName);
+ State state = getState(params, spec);
+ if (state != null) {
+ addTile(spec, appLabel, state, false);
+ continue;
+ }
+ if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
+ continue;
+ }
+ Drawable icon = info.serviceInfo.loadIcon(pm);
+ if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
+ continue;
+ }
+ if (icon == null) {
+ continue;
+ }
+ icon.mutate();
+ icon.setTint(mContext.getColor(android.R.color.white));
+ CharSequence label = info.serviceInfo.loadLabel(pm);
+ addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext);
}
+ mainHandler.post(() -> mListener.onTilesChanged(mTiles));
});
}
- public void setListener(TileStateListener listener) {
- mListener = listener;
+ private State getState(Collection<QSTile<?>> tiles, String spec) {
+ for (QSTile<?> tile : tiles) {
+ if (spec.equals(tile.getTileSpec())) {
+ final QSTile.State state = tile.newTileState();
+ tile.getState().copyTo(state);
+ return state;
+ }
+ }
+ return null;
}
private void addTile(String spec, CharSequence appLabel, State state, boolean isSystem) {
@@ -142,67 +194,6 @@
public boolean isSystem;
}
- private class QueryTilesTask extends
- AsyncTask<Collection<QSTile<?>>, Void, Collection<TileInfo>> {
- @Override
- protected Collection<TileInfo> doInBackground(Collection<QSTile<?>>... params) {
- List<TileInfo> tiles = new ArrayList<>();
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> services = pm.queryIntentServicesAsUser(
- new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser());
- String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock);
- for (ResolveInfo info : services) {
- String packageName = info.serviceInfo.packageName;
- ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
-
- // Don't include apps that are a part of the default tile set.
- if (stockTiles.contains(componentName.flattenToString())) {
- continue;
- }
-
- final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm);
- String spec = CustomTile.toSpec(componentName);
- State state = getState(params[0], spec);
- if (state != null) {
- addTile(spec, appLabel, state, false);
- continue;
- }
- if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) {
- continue;
- }
- Drawable icon = info.serviceInfo.loadIcon(pm);
- if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) {
- continue;
- }
- if (icon == null) {
- continue;
- }
- icon.mutate();
- icon.setTint(mContext.getColor(android.R.color.white));
- CharSequence label = info.serviceInfo.loadLabel(pm);
- addTile(spec, icon, label != null ? label.toString() : "null", appLabel, mContext);
- }
- return tiles;
- }
-
- private State getState(Collection<QSTile<?>> tiles, String spec) {
- for (QSTile<?> tile : tiles) {
- if (spec.equals(tile.getTileSpec())) {
- final QSTile.State state = tile.newTileState();
- tile.getState().copyTo(state);
- return state;
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Collection<TileInfo> result) {
- mTiles.addAll(result);
- mListener.onTilesChanged(mTiles);
- }
- }
-
public interface TileStateListener {
void onTilesChanged(List<TileInfo> tiles);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index b28b0e7..a76299d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -288,7 +288,7 @@
drawable = mTile.getIcon().loadDrawable(mContext);
} catch (Exception e) {
Log.w(TAG, "Invalid icon, forcing into unavailable state");
- tileState = Tile.STATE_UNAVAILABLE;
+ state.state = Tile.STATE_UNAVAILABLE;
drawable = mDefaultIcon.loadDrawable(mContext);
}
state.icon = new DrawableIcon(drawable);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index cda902b..1042356 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -154,6 +154,7 @@
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
public void onActivityPinned() { }
public void onPinnedActivityRestartAttempt(String launchedFromPackage) { }
+ public void onPinnedStackAnimationStarted() { }
public void onPinnedStackAnimationEnded() { }
public void onActivityForcedResizable(String packageName, int taskId) { }
public void onActivityDismissingDockedStack() { }
@@ -206,6 +207,12 @@
}
@Override
+ public void onPinnedStackAnimationStarted() throws RemoteException {
+ mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_STARTED);
+ mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_STARTED);
+ }
+
+ @Override
public void onPinnedStackAnimationEnded() throws RemoteException {
mHandler.removeMessages(H.ON_PINNED_STACK_ANIMATION_ENDED);
mHandler.sendEmptyMessage(H.ON_PINNED_STACK_ANIMATION_ENDED);
@@ -1219,6 +1226,7 @@
private static final int ON_ACTIVITY_FORCED_RESIZABLE = 6;
private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 7;
private static final int ON_TASK_PROFILE_LOCKED = 8;
+ private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
@Override
public void handleMessage(Message msg) {
@@ -1248,6 +1256,12 @@
}
break;
}
+ case ON_PINNED_STACK_ANIMATION_STARTED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onPinnedStackAnimationStarted();
+ }
+ break;
+ }
case ON_PINNED_STACK_ANIMATION_ENDED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
mTaskStackListeners.get(i).onPinnedStackAnimationEnded();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 995901b..09b7bec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -177,6 +177,10 @@
disable(state1, state2, true);
}
+ public void recomputeDisableFlags(boolean animate) {
+ disable(mDisable1, mDisable2, animate);
+ }
+
public void animateExpandNotificationsPanel() {
synchronized (mLock) {
mHandler.removeMessages(MSG_EXPAND_NOTIFICATIONS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 807d902..af464c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -154,7 +154,12 @@
iNotificationManager.getNotificationChannelGroupForPackage(
channel.getGroup(), pkg, appUid);
if (notificationChannelGroup != null) {
- groupName = notificationChannelGroup.getName();
+ if (info != null && notificationChannelGroup.getNameResId() != 0) {
+ groupName = pm.getText(pkg, notificationChannelGroup.getNameResId(), info);
+ }
+ if (notificationChannelGroup.getName() != null) {
+ groupName = notificationChannelGroup.getName();
+ }
}
} catch (RemoteException e) {
Log.e(TAG, e.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 36ed551..8da17fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -41,7 +41,8 @@
* A notification shelf view that is placed inside the notification scroller. It manages the
* overflow icons that don't fit into the regular list anymore.
*/
-public class NotificationShelf extends ActivatableNotificationView {
+public class NotificationShelf extends ActivatableNotificationView implements
+ View.OnLayoutChangeListener {
public static final boolean SHOW_AMBIENT_ICONS = true;
private static final boolean USE_ANIMATIONS_WHEN_OPENING =
@@ -494,6 +495,10 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ updateRelativeOffset();
+ }
+
+ private void updateRelativeOffset() {
mCollapsedIcons.getLocationOnScreen(mTmp);
mRelativeOffset = mTmp[0];
getLocationOnScreen(mTmp);
@@ -560,6 +565,7 @@
public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
mCollapsedIcons = collapsedIcons;
+ mCollapsedIcons.addOnLayoutChangeListener(this);
}
public void setStatusBarState(int statusBarState) {
@@ -595,6 +601,12 @@
}
}
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ updateRelativeOffset();
+ }
+
private class ShelfState extends ExpandableViewState {
private float openedAmount;
private boolean hasItemsInStableShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index aec9a4b..1101701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -612,8 +612,10 @@
}
public void setIconAppearAmount(float iconAppearAmount) {
- mIconAppearAmount = iconAppearAmount;
- invalidate();
+ if (mIconAppearAmount != iconAppearAmount) {
+ mIconAppearAmount = iconAppearAmount;
+ invalidate();
+ }
}
public float getIconAppearAmount() {
@@ -625,8 +627,10 @@
}
public void setDotAppearAmount(float dotAppearAmount) {
- mDotAppearAmount = dotAppearAmount;
- invalidate();
+ if (mDotAppearAmount != dotAppearAmount) {
+ mDotAppearAmount = dotAppearAmount;
+ invalidate();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 1f56c56..6cd3eae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -153,7 +153,7 @@
}
private boolean shouldHideNotificationIcons() {
- return !mStatusBar.isClosed() && mStatusBarComponent.shouldHideNotificationIcons();
+ return !mStatusBar.isClosed() && mStatusBarComponent.hideStatusBarIconsWhenExpanded();
}
public void hideSystemIconArea(boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index ad875f1..3f7e340 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -21,8 +21,8 @@
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
-import android.app.ActivityManager;
import android.annotation.DrawableRes;
+import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 707997d..4581204 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -79,7 +79,9 @@
for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
View child = mNotificationIcons.getChildAt(i);
child.setLayoutParams(params);
- child = mShelfIcons.getChildAt(i);
+ }
+ for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
+ View child = mShelfIcons.getChildAt(i);
child.setLayoutParams(params);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 468fb57..e6d3168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -26,7 +26,6 @@
import android.content.Context;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -47,7 +46,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.DejankUtils;
-import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -179,7 +177,7 @@
private boolean mKeyguardStatusViewAnimating;
private ValueAnimator mQsSizeChangeAnimator;
- private boolean mShadeEmpty;
+ private boolean mShowEmptyShadeView;
private boolean mQsScrimEnabled = true;
private boolean mLastAnnouncementWasQuickSettings;
@@ -211,11 +209,12 @@
}
};
private NotificationGroupManager mGroupManager;
- private boolean mOpening;
+ private boolean mShowIconsWhenExpanded;
private int mIndicationBottomPadding;
private boolean mIsFullWidth;
private boolean mDark;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+ private boolean mNoVisibleNotifications = true;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -1518,7 +1517,7 @@
// it in expanded QS state as well so we don't run into troubles when fading the view in/out
// and expanding/collapsing the whole panel from/to quick settings.
if (mNotificationStackScroller.getNotGoneChildCount() == 0
- && mShadeEmpty) {
+ && mShowEmptyShadeView) {
notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
}
int maxQsHeight = mQsMaxExpansionHeight;
@@ -2118,15 +2117,15 @@
return mDozing;
}
- public void setShadeEmpty(boolean shadeEmpty) {
- mShadeEmpty = shadeEmpty;
+ public void showEmptyShadeView(boolean emptyShadeViewVisible) {
+ mShowEmptyShadeView = emptyShadeViewVisible;
updateEmptyShadeView();
}
private void updateEmptyShadeView() {
// Hide "No notifications" in QS.
- mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded);
+ mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
}
public void setQsScrimEnabled(boolean qsScrimEnabled) {
@@ -2306,7 +2305,7 @@
}
mNotificationStackScroller.setExpandedHeight(expandedHeight);
updateKeyguardBottomAreaAlpha();
- setOpening(isFullWidth() && expandedHeight < getOpeningHeight());
+ updateStatusBarIcons();
}
/**
@@ -2317,13 +2316,21 @@
return mIsFullWidth;
}
- private void setOpening(boolean opening) {
- if (opening != mOpening) {
- mOpening = opening;
+ private void updateStatusBarIcons() {
+ boolean showIconsWhenExpanded = isFullWidth() && getExpandedHeight() < getOpeningHeight();
+ if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
+ showIconsWhenExpanded = false;
+ }
+ if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
+ mShowIconsWhenExpanded = showIconsWhenExpanded;
mStatusBar.recomputeDisableFlags(false);
}
}
+ private boolean isOnKeyguard() {
+ return mStatusBar.getBarState() == StatusBarState.KEYGUARD;
+ }
+
public void setPanelScrimMinFraction(float minFraction) {
mBar.panelScrimMinFractionChanged(minFraction);
}
@@ -2426,12 +2433,8 @@
mGroupManager = groupManager;
}
- public boolean shouldHideNotificationIcons() {
- return !isFullWidth() || (!mOpening && !isFullyCollapsed());
- }
-
- public boolean shouldAnimateIconHiding() {
- return !isFullWidth();
+ public boolean hideStatusBarIconsWhenExpanded() {
+ return !isFullWidth() || !mShowIconsWhenExpanded;
}
private final FragmentListener mFragmentListener = new FragmentListener() {
@@ -2473,4 +2476,8 @@
mKeyguardStatusView.setDark(dark);
positionClockAndNotifications();
}
+
+ public void setNoVisibleNotifications(boolean noNotifications) {
+ mNoVisibleNotifications = noNotifications;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 9e93ed3..bb6c8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.phone;
import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.AlarmManager;
@@ -25,8 +24,6 @@
import android.app.AppGlobals;
import android.app.Notification;
import android.app.Notification.Action;
-import android.app.Notification.BigTextStyle;
-import android.app.Notification.Style;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.SynchronousUserSwitchObserver;
@@ -37,7 +34,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.graphics.drawable.Icon;
import android.media.AudioManager;
@@ -51,7 +47,6 @@
import android.provider.Settings.Global;
import android.service.notification.StatusBarNotification;
import android.telecom.TelecomManager;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -61,11 +56,9 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.R.string;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
-import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.statusbar.CommandQueue;
@@ -87,9 +80,6 @@
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.NotificationChannels;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* This class contains all of the policy about which icons are installed in the status
* bar at boot time. It goes through the normal API for icons, even though it probably
@@ -137,8 +127,6 @@
private boolean mVolumeVisible;
private boolean mCurrentUserSetup;
- private int mZen;
-
private boolean mManagedProfileFocused = false;
private boolean mManagedProfileIconVisible = false;
private boolean mManagedProfileInQuietMode = false;
@@ -275,14 +263,14 @@
@Override
public void onZenChanged(int zen) {
- mZen = zen;
updateVolumeZen();
}
private void updateAlarm() {
final AlarmClockInfo alarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
- final boolean zenNone = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ int zen = mZenController.getZen();
+ final boolean zenNone = zen == Global.ZEN_MODE_NO_INTERRUPTIONS;
mIconController.setIcon(mSlotAlarmClock, zenNone ? R.drawable.stat_sys_alarm_dim
: R.drawable.stat_sys_alarm, null);
mIconController.setIconVisibility(mSlotAlarmClock, mCurrentUserSetup && hasAlarm);
@@ -323,17 +311,18 @@
boolean volumeVisible = false;
int volumeIconId = 0;
String volumeDescription = null;
+ int zen = mZenController.getZen();
if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) {
- zenVisible = mZen != Global.ZEN_MODE_OFF;
- zenIconId = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS
+ zenVisible = zen != Global.ZEN_MODE_OFF;
+ zenIconId = zen == Global.ZEN_MODE_NO_INTERRUPTIONS
? R.drawable.stat_sys_dnd_total_silence : R.drawable.stat_sys_dnd;
zenDescription = mContext.getString(R.string.quick_settings_dnd_label);
- } else if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
+ } else if (zen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
zenVisible = true;
zenIconId = R.drawable.stat_sys_zen_none;
zenDescription = mContext.getString(R.string.interruption_level_none);
- } else if (mZen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
+ } else if (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
zenVisible = true;
zenIconId = R.drawable.stat_sys_zen_important;
zenDescription = mContext.getString(R.string.interruption_level_priority);
@@ -344,7 +333,7 @@
volumeVisible = true;
volumeIconId = R.drawable.stat_sys_ringer_silent;
volumeDescription = mContext.getString(R.string.accessibility_ringer_silent);
- } else if (mZen != Global.ZEN_MODE_NO_INTERRUPTIONS && mZen != Global.ZEN_MODE_ALARMS &&
+ } else if (zen != Global.ZEN_MODE_NO_INTERRUPTIONS && zen != Global.ZEN_MODE_ALARMS &&
audioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
volumeVisible = true;
volumeIconId = R.drawable.stat_sys_ringer_vibrate;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 53d2d73..816a39d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -172,7 +172,6 @@
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.InflationException;
-import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
@@ -717,7 +716,7 @@
private LogMaker mStatusBarStateLog;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private NotificationIconAreaController mNotificationIconAreaController;
- private ConfigurationListener mDensityChangeListener;
+ private ConfigurationListener mConfigurationListener;
private InflationExceptionHandler mInflationExceptionHandler = this::handleInflationException;
private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
@@ -945,13 +944,18 @@
Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(this);
- mDensityChangeListener = new ConfigurationListener() {
+ mConfigurationListener = new ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ StatusBar.this.onConfigurationChanged(newConfig);
+ }
+
@Override
public void onDensityOrFontScaleChanged() {
StatusBar.this.onDensityOrFontScaleChanged();
}
};
- Dependency.get(ConfigurationController.class).addCallback(mDensityChangeListener);
+ Dependency.get(ConfigurationController.class).addCallback(mConfigurationListener);
}
protected void createIconController() {
@@ -2034,10 +2038,10 @@
}
private void updateEmptyShadeView() {
- boolean showEmptyShade =
+ boolean showEmptyShadeView =
mState != StatusBarState.KEYGUARD &&
mNotificationData.getActiveNotifications().size() == 0;
- mNotificationPanel.setShadeEmpty(showEmptyShade);
+ mNotificationPanel.showEmptyShadeView(showEmptyShadeView);
}
private void updateSpeedBumpIndex() {
@@ -2506,7 +2510,7 @@
* This needs to be called if state used by {@link #adjustDisableFlags} changes.
*/
public void recomputeDisableFlags(boolean animate) {
- mCommandQueue.disable(mDisabledUnmodified1, mDisabledUnmodified2, animate);
+ mCommandQueue.recomputeDisableFlags(animate);
}
protected H createHandler() {
@@ -2720,8 +2724,8 @@
return mLaunchTransitionFadingAway;
}
- public boolean shouldHideNotificationIcons() {
- return mNotificationPanel.shouldHideNotificationIcons();
+ public boolean hideStatusBarIconsWhenExpanded() {
+ return mNotificationPanel.hideStatusBarIconsWhenExpanded();
}
/**
@@ -2963,7 +2967,7 @@
runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
showBouncerIfKeyguard();
- recomputeDisableFlags(shouldAnimatIconHiding() /* animate */);
+ recomputeDisableFlags(mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);
// Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
// the bouncer appear animation.
@@ -2972,10 +2976,6 @@
}
}
- private boolean shouldAnimatIconHiding() {
- return mNotificationPanel.shouldAnimateIconHiding();
- }
-
public boolean interceptTouchEvent(MotionEvent event) {
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
@@ -3910,7 +3910,7 @@
}
Dependency.get(ActivityStarterDelegate.class).setActivityStarterImpl(null);
mDeviceProvisionedController.removeCallback(mUserSetupObserver);
- Dependency.get(ConfigurationController.class).removeCallback(mDensityChangeListener);
+ Dependency.get(ConfigurationController.class).removeCallback(mConfigurationListener);
}
private boolean mDemoModeAllowed;
@@ -6570,6 +6570,7 @@
}
}
}
+ mNotificationPanel.setNoVisibleNotifications(visibleNotifications == 0);
mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
new file mode 100644
index 0000000..b983820
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.SysUIRunner;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTile.State;
+import com.android.systemui.statusbar.phone.QSTileHost;
+
+import com.android.systemui.utils.TestableLooper;
+import com.android.systemui.utils.TestableLooper.MessageHandler;
+import com.android.systemui.utils.TestableLooper.RunWithLooper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(SysUIRunner.class)
+@RunWithLooper
+public class TileQueryHelperTest extends SysuiTestCase {
+ private TestableLooper mBGLooper;
+ private Runnable mLastCallback;
+
+ @Before
+ public void setup() {
+ mBGLooper = TestableLooper.get(this);
+ injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper());
+ }
+
+ @Test
+ public void testCompletionCalled() {
+ QSTileHost mockHost = mock(QSTileHost.class);
+ TileAdapter mockAdapter = mock(TileAdapter.class);
+ Runnable mockCompletion = mock(Runnable.class);
+ new TileQueryHelper(mContext, mockHost, mockAdapter, mockCompletion);
+ mBGLooper.processAllMessages();
+ verify(mockCompletion).run();
+ }
+
+ @Test
+ public void testCompletionCalledAfterTilesFetched() {
+ QSTile mockTile = mock(QSTile.class);
+ State mockState = mock(State.class);
+ when(mockTile.newTileState()).thenReturn(mockState);
+ when(mockTile.getState()).thenReturn(mockState);
+ when(mockTile.isAvailable()).thenReturn(true);
+
+ QSTileHost mockHost = mock(QSTileHost.class);
+ when(mockHost.createTile(any())).thenReturn(mockTile);
+
+ mBGLooper.setMessageHandler((Message m) -> {
+ mLastCallback = m.getCallback();
+ return true;
+ });
+ TileAdapter mockAdapter = mock(TileAdapter.class);
+ Runnable mockCompletion = mock(Runnable.class);
+ new TileQueryHelper(mContext, mockHost, mockAdapter, mockCompletion);
+
+ // Verify that the last thing in the queue was our callback
+ mBGLooper.processAllMessages();
+ assertEquals(mockCompletion, mLastCallback);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 9a3fabb..92f8c7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -100,10 +100,6 @@
mNotificationChannel = new NotificationChannel(
TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
- when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME),
- eq(R.string.notification_menu_accessibility), anyObject())).thenReturn(
- getContext().getString(R.string.notification_menu_accessibility));
-
when(mMockINotificationManager.getNumNotificationChannelsForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(1);
}
@@ -174,6 +170,28 @@
@Test
@UiThreadTest
+ public void testBindNotification_SetsGroupName_resId() throws Exception {
+ when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME),
+ eq(R.string.legacy_vpn_name), anyObject())).thenReturn(
+ getContext().getString(R.string.legacy_vpn_name));
+ mNotificationChannel.setGroup("test_group_id");
+ final NotificationChannelGroup notificationChannelGroup =
+ new NotificationChannelGroup("test_group_id", R.string.legacy_vpn_name);
+ when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+ eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt()))
+ .thenReturn(notificationChannelGroup);
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ mMockStatusBarNotification, mNotificationChannel, null, null, null);
+ final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name);
+ assertEquals(View.VISIBLE, groupNameView.getVisibility());
+ assertEquals(mContext.getString(R.string.legacy_vpn_name), groupNameView.getText());
+ final TextView groupDividerView =
+ (TextView) mNotificationInfo.findViewById(R.id.pkg_group_divider);
+ assertEquals(View.VISIBLE, groupDividerView.getVisibility());
+ }
+
+ @Test
+ @UiThreadTest
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
@@ -184,6 +202,9 @@
@Test
@UiThreadTest
public void testBindNotification_SetsTextChannelName_resId() throws Exception {
+ when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME),
+ eq(R.string.notification_menu_accessibility), anyObject())).thenReturn(
+ getContext().getString(R.string.notification_menu_accessibility));
NotificationChannel notificationChannelResId = new NotificationChannel(
TEST_CHANNEL, R.string.notification_menu_accessibility,
NotificationManager.IMPORTANCE_LOW);
diff --git a/pathmap.mk b/pathmap.mk
new file mode 100644
index 0000000..c241d99
--- /dev/null
+++ b/pathmap.mk
@@ -0,0 +1,64 @@
+#
+# Copyright (C) 2016 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.
+#
+
+#
+# A central place to define mappings to paths used by the framework build, to
+# avoid hard-coding them in Android.mk files. Not meant for header file include
+# directories, despite the fact that it was historically used for that!
+#
+
+# Import path mappings from the support library project. This will set up
+# FRAMEWORKS_SUPPORT_JAVA_SRC_DIRS and FRAMEWORKS_SUPPORT_JAVA_LIBRARIES for
+# use later in this file.
+include $(LOCAL_PATH)/../support/pathmap.mk
+
+#
+# A list of all source roots under frameworks/multidex.
+#
+FRAMEWORKS_MULTIDEX_SUBDIRS := \
+ multidex/library/src \
+ multidex/instrumentation/src
+
+#
+# A version of FRAMEWORKS_SUPPORT_SUBDIRS that is expanded to full paths from
+# the root of the tree.
+#
+FRAMEWORKS_SUPPORT_JAVA_SRC_DIRS += \
+ $(addprefix frameworks/,$(FRAMEWORKS_MULTIDEX_SUBDIRS)) \
+ frameworks/rs/support
+
+#
+# A list of support library modules.
+#
+FRAMEWORKS_SUPPORT_JAVA_LIBRARIES += \
+ android-support-v8-renderscript \
+ android-support-multidex \
+ android-support-multidex-instrumentation
+
+#
+# A list of all documented source roots under frameworks/data-binding.
+#
+FRAMEWORKS_DATA_BINDING_SUBDIRS := \
+ baseLibrary/src/main \
+ extensions/library/src/main \
+ extensions/library/src/doc
+
+#
+# A version of FRAMEWORKS_DATA_BINDING_SUBDIRS that is expanded to full paths from
+# the root of the tree.
+#
+FRAMEWORKS_DATA_BINDING_JAVA_SRC_DIRS := \
+ $(addprefix frameworks/data-binding/,$(FRAMEWORKS_DATA_BINDING_SUBDIRS))
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index c50623e..8aa37ef 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1603,7 +1603,7 @@
@Override
public boolean requestPinAppWidget(String callingPackage, ComponentName componentName,
- IntentSender resultSender) {
+ Bundle extras, IntentSender resultSender) {
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
@@ -1628,7 +1628,7 @@
}
return LocalServices.getService(ShortcutServiceInternal.class)
- .requestPinAppWidget(callingPackage, info, resultSender, userId);
+ .requestPinAppWidget(callingPackage, info, extras, resultSender, userId);
}
@Override
diff --git a/services/core/Android.mk b/services/core/Android.mk
index cd88b85..1864d34 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -19,7 +19,6 @@
LOCAL_JAVA_LIBRARIES := \
services.net \
- telephony-common \
android.hardware.light@2.0-java \
android.hardware.power@1.0-java \
android.hardware.tv.cec@1.0-java
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ea33906..531df81 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -57,9 +57,8 @@
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ITelephonyRegistry;
import com.android.internal.telephony.IPhoneStateListener;
-import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.PhoneConstantConversions;
import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.ServiceStateTracker;
import com.android.internal.telephony.TelephonyIntents;
import com.android.server.am.BatteryStatsService;
@@ -171,7 +170,7 @@
private int[] mDataConnectionNetworkType;
- private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN;
+ private int mOtaspMode = TelephonyManager.OTASP_UNKNOWN;
private ArrayList<List<CellInfo>> mCellInfo = null;
@@ -1497,7 +1496,7 @@
Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
intent.putExtra(PhoneConstants.STATE_KEY,
- DefaultPhoneNotifier.convertCallState(state).toString());
+ PhoneConstantConversions.convertCallState(state).toString());
if (!TextUtils.isEmpty(incomingNumber)) {
intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber);
}
@@ -1531,7 +1530,7 @@
// required info.
Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
intent.putExtra(PhoneConstants.STATE_KEY,
- DefaultPhoneNotifier.convertDataState(state).toString());
+ PhoneConstantConversions.convertDataState(state).toString());
if (!isDataConnectivityPossible) {
intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e9e73cc..54ee5dc 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12111,6 +12111,12 @@
mRecentTasks.notifyTaskPersisterLocked(task, flush);
}
+ /** Notifies all listeners when the pinned stack animation starts. */
+ @Override
+ public void notifyPinnedStackAnimationStarted() {
+ mTaskChangeNotificationController.notifyPinnedStackAnimationStarted();
+ }
+
/** Notifies all listeners when the pinned stack animation ends. */
@Override
public void notifyPinnedStackAnimationEnded() {
@@ -19013,8 +19019,7 @@
return ActivityManager.BROADCAST_SUCCESS;
}
- final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
- int skipCount, long dispatchTime) {
+ final void rotateBroadcastStatsIfNeededLocked() {
final long now = SystemClock.elapsedRealtime();
if (mCurBroadcastStats == null ||
(mCurBroadcastStats.mStartRealtime +(24*60*60*1000) < now)) {
@@ -19025,9 +19030,19 @@
}
mCurBroadcastStats = new BroadcastStats();
}
+ }
+
+ final void addBroadcastStatLocked(String action, String srcPackage, int receiveCount,
+ int skipCount, long dispatchTime) {
+ rotateBroadcastStatsIfNeededLocked();
mCurBroadcastStats.addBroadcast(action, srcPackage, receiveCount, skipCount, dispatchTime);
}
+ final void addBackgroundCheckViolationLocked(String action, String targetPackage) {
+ rotateBroadcastStatsIfNeededLocked();
+ mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
+ }
+
final Intent verifyBroadcastLocked(Intent intent) {
// Refuse possible leaked file descriptors
if (intent != null && intent.hasFileDescriptors() == true) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 75b5929..c9d19cb 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1195,6 +1195,8 @@
&& r.intent.getPackage() == null
&& ((r.intent.getFlags()
& Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0))) {
+ mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
+ component.getPackageName());
Slog.w(TAG, "Background execution not allowed: receiving "
+ r.intent + " to "
+ component.flattenToShortString());
diff --git a/services/core/java/com/android/server/am/BroadcastStats.java b/services/core/java/com/android/server/am/BroadcastStats.java
index fdbaada..fd24582 100644
--- a/services/core/java/com/android/server/am/BroadcastStats.java
+++ b/services/core/java/com/android/server/am/BroadcastStats.java
@@ -47,6 +47,7 @@
static final class ActionEntry {
final String mAction;
final ArrayMap<String, PackageEntry> mPackages = new ArrayMap<>();
+ final ArrayMap<String, ViolationEntry> mBackgroundCheckViolations = new ArrayMap<>();
int mReceiveCount;
int mSkipCount;
long mTotalDispatchTime;
@@ -61,6 +62,10 @@
int mSendCount;
}
+ static final class ViolationEntry {
+ int mCount;
+ }
+
public BroadcastStats() {
mStartRealtime = SystemClock.elapsedRealtime();
mStartUptime = SystemClock.uptimeMillis();
@@ -87,6 +92,20 @@
pe.mSendCount++;
}
+ public void addBackgroundCheckViolation(String action, String targetPackage) {
+ ActionEntry ae = mActions.get(action);
+ if (ae == null) {
+ ae = new ActionEntry(action);
+ mActions.put(action, ae);
+ }
+ ViolationEntry ve = ae.mBackgroundCheckViolations.get(targetPackage);
+ if (ve == null) {
+ ve = new ViolationEntry();
+ ae.mBackgroundCheckViolations.put(targetPackage, ve);
+ }
+ ve.mCount++;
+ }
+
public boolean dumpStats(PrintWriter pw, String prefix, String dumpPackage) {
boolean printedSomething = false;
ArrayList<ActionEntry> actions = new ArrayList<>(mActions.size());
@@ -123,6 +142,15 @@
pw.print(pe.mSendCount);
pw.println(" times");
}
+ for (int j=ae.mBackgroundCheckViolations.size()-1; j>=0; j--) {
+ pw.print(prefix);
+ pw.print(" Bg Check Violation ");
+ pw.print(ae.mBackgroundCheckViolations.keyAt(j));
+ pw.print(": ");
+ ViolationEntry ve = ae.mBackgroundCheckViolations.valueAt(j);
+ pw.print(ve.mCount);
+ pw.println(" times");
+ }
}
return printedSomething;
}
@@ -158,6 +186,14 @@
pw.print(pe.mSendCount);
pw.println();
}
+ for (int j=ae.mBackgroundCheckViolations.size()-1; j>=0; j--) {
+ pw.print("v,");
+ pw.print(ae.mBackgroundCheckViolations.keyAt(j));
+ ViolationEntry ve = ae.mBackgroundCheckViolations.valueAt(j);
+ pw.print(",");
+ pw.print(ve.mCount);
+ pw.println();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
index 9dfc7cd..9a98bc6 100644
--- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java
@@ -46,6 +46,7 @@
static final int NOTIFY_TASK_REMOVAL_STARTED_LISTENERS = 13;
static final int NOTIFY_TASK_PROFILE_LOCKED_LISTENERS_MSG = 14;
static final int NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG = 15;
+ static final int NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG = 16;
// Delay in notifying task stack change listeners (in millis)
static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -100,6 +101,10 @@
l.onPinnedActivityRestartAttempt((String) m.obj);
};
+ private final TaskStackConsumer mNotifyPinnedStackAnimationStarted = (l, m) -> {
+ l.onPinnedStackAnimationStarted();
+ };
+
private final TaskStackConsumer mNotifyPinnedStackAnimationEnded = (l, m) -> {
l.onPinnedStackAnimationEnded();
};
@@ -166,6 +171,9 @@
case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG:
forAllRemoteListeners(mNotifyPinnedActivityRestartAttempt, msg);
break;
+ case NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG:
+ forAllRemoteListeners(mNotifyPinnedStackAnimationStarted, msg);
+ break;
case NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG:
forAllRemoteListeners(mNotifyPinnedStackAnimationEnded, msg);
break;
@@ -276,6 +284,15 @@
msg.sendToTarget();
}
+ /** Notifies all listeners when the pinned stack animation starts. */
+ void notifyPinnedStackAnimationStarted() {
+ mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG);
+ final Message msg =
+ mHandler.obtainMessage(NOTIFY_PINNED_STACK_ANIMATION_STARTED_LISTENERS_MSG);
+ forAllLocalListeners(mNotifyPinnedStackAnimationStarted, msg);
+ msg.sendToTarget();
+ }
+
/** Notifies all listeners when the pinned stack animation ends. */
void notifyPinnedStackAnimationEnded() {
mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index e72f7ff..476eb10 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -192,9 +192,14 @@
if (TAG_GROUP.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
+ int groupNameRes = safeInt(parser, ATT_NAME_RES_ID, 0);
if (!TextUtils.isEmpty(id)) {
- final NotificationChannelGroup group =
- new NotificationChannelGroup(id, groupName);
+ NotificationChannelGroup group = null;
+ if (groupName != null) {
+ group = new NotificationChannelGroup(id, groupName);
+ } else {
+ group = new NotificationChannelGroup(id, groupNameRes);
+ }
r.groups.put(id, group);
}
}
@@ -202,7 +207,7 @@
if (TAG_CHANNEL.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
- int channelNameRes = safeInt(parser, ATT_NAME_RES_ID, -1);
+ int channelNameRes = safeInt(parser, ATT_NAME_RES_ID, 0);
int channelImportance =
safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
@@ -473,7 +478,8 @@
Preconditions.checkNotNull(pkg);
Preconditions.checkNotNull(group);
Preconditions.checkNotNull(group.getId());
- Preconditions.checkNotNull(group.getName());
+ Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName())
+ || group.getNameResId() != 0);
Record r = getOrCreateRecord(pkg, uid);
if (r == null) {
throw new IllegalArgumentException("Invalid package");
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index ba4d46a..a692559 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -44,8 +44,11 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemProperties;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
@@ -70,6 +73,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -193,6 +197,14 @@
static final String PERMISSION_DENIED = "Operation not permitted for user shell";
+ /**
+ * The system property that specifies the default overlays to apply.
+ * This is a semicolon separated list of package names.
+ *
+ * Ex: com.android.vendor.overlay_one;com.android.vendor.overlay_two
+ */
+ private static final String DEFAULT_OVERLAYS_PROP = "ro.boot.vendor.overlay.theme";
+
private final Object mLock = new Object();
private final AtomicFile mSettingsFile;
@@ -216,7 +228,8 @@
mUserManager = UserManagerService.getInstance();
IdmapManager im = new IdmapManager(installer);
mSettings = new OverlayManagerSettings();
- mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings);
+ mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
+ getDefaultOverlayPackages());
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(ACTION_PACKAGE_ADDED);
@@ -257,6 +270,21 @@
updateAssets(newUserId, targets);
}
+ private static Set<String> getDefaultOverlayPackages() {
+ final String str = SystemProperties.get(DEFAULT_OVERLAYS_PROP);
+ if (TextUtils.isEmpty(str)) {
+ return Collections.emptySet();
+ }
+
+ final ArraySet<String> defaultPackages = new ArraySet<>();
+ for (String packageName : str.split(";")) {
+ if (!TextUtils.isEmpty(packageName)) {
+ defaultPackages.add(packageName);
+ }
+ }
+ return defaultPackages;
+ }
+
private final class PackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull final Context context, @NonNull final Intent intent) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 0e33409..ed49383 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -27,8 +27,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.om.OverlayInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -53,13 +53,16 @@
private final PackageManagerHelper mPackageManager;
private final IdmapManager mIdmapManager;
private final OverlayManagerSettings mSettings;
+ private final Set<String> mDefaultOverlays;
OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager,
@NonNull final IdmapManager idmapManager,
- @NonNull final OverlayManagerSettings settings) {
+ @NonNull final OverlayManagerSettings settings,
+ @NonNull final Set<String> defaultOverlays) {
mPackageManager = packageManager;
mIdmapManager = idmapManager;
mSettings = settings;
+ mDefaultOverlays = defaultOverlays;
}
/*
@@ -92,12 +95,22 @@
final PackageInfo overlayPackage = overlayPackages.get(i);
final OverlayInfo oi = storedOverlayInfos.get(overlayPackage.packageName);
if (oi == null || !oi.targetPackageName.equals(overlayPackage.overlayTarget)) {
- if (oi != null) {
- packagesToUpdateAssets.add(oi.targetPackageName);
- }
+ // Update the overlay if it didn't exist or had the wrong target package.
mSettings.init(overlayPackage.packageName, newUserId,
overlayPackage.overlayTarget,
overlayPackage.applicationInfo.getBaseCodePath());
+
+ if (oi == null) {
+ // This overlay does not exist in our settings.
+ if (mDefaultOverlays.contains(overlayPackage.packageName)) {
+ // Enable this overlay by default.
+ mSettings.setEnabled(overlayPackage.packageName, newUserId, true);
+ }
+ } else {
+ // The targetPackageName we have stored doesn't match the overlay's target.
+ // Queue the old target for an update as well.
+ packagesToUpdateAssets.add(oi.targetPackageName);
+ }
}
try {
@@ -132,7 +145,7 @@
}
}
- return new ArrayList<String>(packagesToUpdateAssets);
+ return new ArrayList<>(packagesToUpdateAssets);
}
void onUserRemoved(final int userId) {
@@ -303,6 +316,7 @@
void onDump(@NonNull final PrintWriter pw) {
mSettings.dump(pw);
+ pw.println("Default overlays: " + TextUtils.join(";", mDefaultOverlays));
}
List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName,
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 44908a7..ff5c594 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -45,7 +45,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
-import java.util.Map;
/**
* Data structure representing the current state of all overlay packages in the
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index caacc46..1fe4ccc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -496,18 +496,6 @@
private static final String PACKAGE_SCHEME = "package";
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
- /**
- * If VENDOR_OVERLAY_THEME_PROPERTY is set, search for runtime resource overlay APKs also in
- * VENDOR_OVERLAY_DIR/<value of VENDOR_OVERLAY_THEME_PROPERTY> in addition to
- * VENDOR_OVERLAY_DIR.
- */
- private static final String VENDOR_OVERLAY_THEME_PROPERTY = "ro.boot.vendor.overlay.theme";
- /**
- * Same as VENDOR_OVERLAY_THEME_PROPERTY, except persistent. If set will override whatever
- * is in VENDOR_OVERLAY_THEME_PROPERTY.
- */
- private static final String VENDOR_OVERLAY_THEME_PERSIST_PROPERTY
- = "persist.vendor.overlay.theme";
/** Permission grant: not grant the permission. */
private static final int GRANT_DENIED = 1;
@@ -2476,16 +2464,6 @@
// Collect vendor overlay packages. (Do this before scanning any apps.)
// For security and version matching reason, only consider
// overlay packages if they reside in the right directory.
- String overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PERSIST_PROPERTY);
- if (overlayThemeDir.isEmpty()) {
- overlayThemeDir = SystemProperties.get(VENDOR_OVERLAY_THEME_PROPERTY);
- }
- if (!overlayThemeDir.isEmpty()) {
- scanDirTracedLI(new File(VENDOR_OVERLAY_DIR, overlayThemeDir), mDefParseFlags
- | PackageParser.PARSE_IS_SYSTEM
- | PackageParser.PARSE_IS_SYSTEM_DIR
- | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
- }
scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR
@@ -3415,6 +3393,8 @@
return null;
}
+ rebaseEnabledOverlays(packageInfo.applicationInfo, userId);
+
packageInfo.packageName = packageInfo.applicationInfo.packageName =
resolveExternalPackageNameLPr(p);
@@ -4147,8 +4127,12 @@
if (a != null && mSettings.isEnabledAndMatchLPr(a.info, flags, userId)) {
PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
if (ps == null) return null;
- return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
- userId);
+ ActivityInfo ri = PackageParser.generateActivityInfo(a, flags,
+ ps.readUserState(userId), userId);
+ if (ri != null) {
+ rebaseEnabledOverlays(ri.applicationInfo, userId);
+ }
+ return ri;
}
}
return null;
@@ -4274,8 +4258,12 @@
if (s != null && mSettings.isEnabledAndMatchLPr(s.info, flags, userId)) {
PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
if (ps == null) return null;
- return PackageParser.generateServiceInfo(s, flags, ps.readUserState(userId),
- userId);
+ ServiceInfo si = PackageParser.generateServiceInfo(s, flags,
+ ps.readUserState(userId), userId);
+ if (si != null) {
+ rebaseEnabledOverlays(si.applicationInfo, userId);
+ }
+ return si;
}
}
return null;
@@ -4294,8 +4282,12 @@
if (p != null && mSettings.isEnabledAndMatchLPr(p.info, flags, userId)) {
PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
if (ps == null) return null;
- return PackageParser.generateProviderInfo(p, flags, ps.readUserState(userId),
- userId);
+ ProviderInfo pi = PackageParser.generateProviderInfo(p, flags,
+ ps.readUserState(userId), userId);
+ if (pi != null) {
+ rebaseEnabledOverlays(pi.applicationInfo, userId);
+ }
+ return pi;
}
}
return null;
@@ -15919,14 +15911,6 @@
final PackageSetting ps = mSettings.mPackages.get(pkgName);
- // don't allow an upgrade from full to ephemeral
- if (isInstantApp && !ps.getInstantApp(user.getIdentifier())) {
- // can't downgrade from full to instant
- Slog.w(TAG, "Can't replace app with instant app: " + pkgName);
- res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
- return;
- }
-
// verify signatures are valid
if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
if (!checkUpgradeKeySetLP(ps, pkg)) {
@@ -15984,6 +15968,27 @@
// In case of rollback, remember per-user/profile install state
allUsers = sUserManager.getUserIds();
installedUsers = ps.queryInstalledUsers(allUsers, true);
+
+ // don't allow an upgrade from full to ephemeral
+ if (isInstantApp) {
+ if (user == null || user.getIdentifier() == UserHandle.USER_ALL) {
+ for (int currentUser : allUsers) {
+ if (!ps.getInstantApp(currentUser)) {
+ // can't downgrade from full to instant
+ Slog.w(TAG, "Can't replace full app with instant app: " + pkgName
+ + " for user: " + currentUser);
+ res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
+ return;
+ }
+ }
+ } else if (!ps.getInstantApp(user.getIdentifier())) {
+ // can't downgrade from full to instant
+ Slog.w(TAG, "Can't replace full app with instant app: " + pkgName
+ + " for user: " + user.getIdentifier());
+ res.setReturnCode(PackageManager.INSTALL_FAILED_INSTANT_APP_INVALID);
+ return;
+ }
+ }
}
// Update what is removed
@@ -23207,7 +23212,7 @@
ArrayList<String> paths = null;
if (overlayPackageNames != null) {
final int N = overlayPackageNames.size();
- paths = new ArrayList<String>(N);
+ paths = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final String packageName = overlayPackageNames.get(i);
final PackageParser.Package pkg = mPackages.get(packageName);
@@ -23222,7 +23227,7 @@
ArrayMap<String, ArrayList<String>> userSpecificOverlays =
mEnabledOverlayPaths.get(userId);
if (userSpecificOverlays == null) {
- userSpecificOverlays = new ArrayMap<String, ArrayList<String>>();
+ userSpecificOverlays = new ArrayMap<>();
mEnabledOverlayPaths.put(userId, userSpecificOverlays);
}
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index b0689b8..8a8128d 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -26,6 +26,7 @@
import android.content.pm.LauncherApps.PinItemRequest;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@@ -63,14 +64,21 @@
mLauncherUid = launcherUid;
}
+ @Override
public ShortcutInfo getShortcutInfo() {
return null;
}
+ @Override
public AppWidgetProviderInfo getAppWidgetProviderInfo() {
return null;
}
+ @Override
+ public Bundle getExtras() {
+ return null;
+ }
+
/**
* Returns true if the caller is same as the default launcher app when this request
* object was created.
@@ -136,19 +144,26 @@
*/
private static class PinAppWidgetRequestInner extends PinItemRequestInner {
final AppWidgetProviderInfo mAppWidgetProviderInfo;
+ final Bundle mExtras;
private PinAppWidgetRequestInner(ShortcutRequestPinProcessor processor,
IntentSender resultIntent, int launcherUid,
- AppWidgetProviderInfo appWidgetProviderInfo) {
+ AppWidgetProviderInfo appWidgetProviderInfo, Bundle extras) {
super(processor, resultIntent, launcherUid);
mAppWidgetProviderInfo = appWidgetProviderInfo;
+ mExtras = extras;
}
@Override
public AppWidgetProviderInfo getAppWidgetProviderInfo() {
return mAppWidgetProviderInfo;
}
+
+ @Override
+ public Bundle getExtras() {
+ return mExtras;
+ }
}
/**
@@ -212,7 +227,7 @@
* always null.
*/
public boolean requestPinItemLocked(ShortcutInfo inShortcut, AppWidgetProviderInfo inAppWidget,
- int userId, IntentSender resultIntent) {
+ Bundle extras, int userId, IntentSender resultIntent) {
// First, make sure the launcher supports it.
@@ -242,7 +257,8 @@
int launcherUid = mService.injectGetPackageUid(
confirmActivity.first.getPackageName(), launcherUserId);
request = new PinItemRequest(
- new PinAppWidgetRequestInner(this, resultIntent, launcherUid, inAppWidget),
+ new PinAppWidgetRequestInner(this, resultIntent, launcherUid, inAppWidget,
+ extras),
PinItemRequest.REQUEST_TYPE_APPWIDGET);
}
return startRequestConfirmActivity(confirmActivity.first, launcherUserId, request,
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 43288cd..1aaec1a 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -59,6 +59,7 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
@@ -1867,7 +1868,7 @@
IntentSender resultIntent, int userId) {
Preconditions.checkNotNull(shortcut);
Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
- return requestPinItem(packageName, userId, shortcut, null, resultIntent);
+ return requestPinItem(packageName, userId, shortcut, null, null, resultIntent);
}
@Override
@@ -1894,8 +1895,8 @@
* After validating the caller, it passes the request to {@link #mShortcutRequestPinProcessor}.
* Either {@param shortcut} or {@param appWidget} should be non-null.
*/
- private boolean requestPinItem(String packageName, int userId,
- ShortcutInfo shortcut, AppWidgetProviderInfo appWidget, IntentSender resultIntent) {
+ private boolean requestPinItem(String packageName, int userId, ShortcutInfo shortcut,
+ AppWidgetProviderInfo appWidget, Bundle extras, IntentSender resultIntent) {
verifyCaller(packageName, userId);
final boolean ret;
@@ -1906,8 +1907,8 @@
"Calling application must have a foreground activity or a foreground service");
// Send request to the launcher, if supported.
- ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, userId,
- resultIntent);
+ ret = mShortcutRequestPinProcessor.requestPinItemLocked(shortcut, appWidget, extras,
+ userId, resultIntent);
}
verifyStates();
@@ -2649,10 +2650,10 @@
@Override
public boolean requestPinAppWidget(@NonNull String callingPackage,
- @NonNull AppWidgetProviderInfo appWidget, @Nullable IntentSender resultIntent,
- int userId) {
+ @NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
+ @Nullable IntentSender resultIntent, int userId) {
Preconditions.checkNotNull(appWidget);
- return requestPinItem(callingPackage, userId, null, appWidget, resultIntent);
+ return requestPinItem(callingPackage, userId, null, appWidget, extras, resultIntent);
}
@Override
diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
index 10d30aa..c064392 100644
--- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
+++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java
@@ -34,21 +34,37 @@
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.format.DateUtils;
+import android.util.Pair;
import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
import com.android.server.pm.Installer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
-
/**
* CacheQuotaStrategy is a strategy for determining cache quotas using usage stats and foreground
* time using the calculation as defined in the refuel rocket.
@@ -58,17 +74,28 @@
private final Object mLock = new Object();
+ // XML Constants
+ private static final String CACHE_INFO_TAG = "cache-info";
+ private static final String ATTR_PREVIOUS_BYTES = "previousBytes";
+ private static final String TAG_QUOTA = "quota";
+ private static final String ATTR_UUID = "uuid";
+ private static final String ATTR_UID = "uid";
+ private static final String ATTR_QUOTA_IN_BYTES = "bytes";
+
private final Context mContext;
private final UsageStatsManagerInternal mUsageStats;
private final Installer mInstaller;
private ServiceConnection mServiceConnection;
private ICacheQuotaService mRemoteService;
+ private AtomicFile mPreviousValuesFile;
public CacheQuotaStrategy(
Context context, UsageStatsManagerInternal usageStatsManager, Installer installer) {
mContext = Preconditions.checkNotNull(context);
mUsageStats = Preconditions.checkNotNull(usageStatsManager);
mInstaller = Preconditions.checkNotNull(installer);
+ mPreviousValuesFile = new AtomicFile(new File(
+ new File(Environment.getDataDirectory(), "system"), "cachequota.xml"));
}
/**
@@ -128,7 +155,7 @@
}
/**
- * Returns a list of CacheQuotaRequests which do not have their quotas filled out for apps
+ * Returns a list of CacheQuotaHints which do not have their quotas filled out for apps
* which have been used in the last year.
*/
private List<CacheQuotaHint> getUnfulfilledRequests() {
@@ -176,6 +203,11 @@
final List<CacheQuotaHint> processedRequests =
data.getParcelableArrayList(
CacheQuotaService.REQUEST_LIST_KEY);
+ pushProcessedQuotas(processedRequests);
+ writeXmlToFile(processedRequests);
+ }
+
+ private void pushProcessedQuotas(List<CacheQuotaHint> processedRequests) {
final int requestSize = processedRequests.size();
for (int i = 0; i < requestSize; i++) {
CacheQuotaHint request = processedRequests.get(i);
@@ -200,8 +232,10 @@
}
private void disconnectService() {
- mContext.unbindService(mServiceConnection);
- mServiceConnection = null;
+ if (mServiceConnection != null) {
+ mContext.unbindService(mServiceConnection);
+ mServiceConnection = null;
+ }
}
private ComponentName getServiceComponentName() {
@@ -223,4 +257,131 @@
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
return new ComponentName(serviceInfo.packageName, serviceInfo.name);
}
+
+ private void writeXmlToFile(List<CacheQuotaHint> processedRequests) {
+ FileOutputStream fileStream = null;
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ fileStream = mPreviousValuesFile.startWrite();
+ out.setOutput(fileStream, StandardCharsets.UTF_8.name());
+ saveToXml(out, processedRequests, 0);
+ mPreviousValuesFile.finishWrite(fileStream);
+ } catch (Exception e) {
+ Slog.e(TAG, "An error occurred while writing the cache quota file.", e);
+ mPreviousValuesFile.failWrite(fileStream);
+ }
+ }
+
+ /**
+ * Initializes the quotas from the file.
+ * @return the number of bytes that were free on the device when the quotas were last calced.
+ */
+ public long setupQuotasFromFile() throws IOException {
+ FileInputStream stream;
+ try {
+ stream = mPreviousValuesFile.openRead();
+ } catch (FileNotFoundException e) {
+ // The file may not exist yet -- this isn't truly exceptional.
+ return -1;
+ }
+
+ Pair<Long, List<CacheQuotaHint>> cachedValues = null;
+ try {
+ cachedValues = readFromXml(stream);
+ } catch (XmlPullParserException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+
+ if (cachedValues == null) {
+ Slog.e(TAG, "An error occurred while parsing the cache quota file.");
+ return -1;
+ }
+ pushProcessedQuotas(cachedValues.second);
+ return cachedValues.first;
+ }
+
+ @VisibleForTesting
+ static void saveToXml(XmlSerializer out,
+ List<CacheQuotaHint> requests, long bytesWhenCalculated) throws IOException {
+ out.startDocument(null, true);
+ out.startTag(null, CACHE_INFO_TAG);
+ int requestSize = requests.size();
+ out.attribute(null, ATTR_PREVIOUS_BYTES, Long.toString(bytesWhenCalculated));
+
+ for (int i = 0; i < requestSize; i++) {
+ CacheQuotaHint request = requests.get(i);
+ out.startTag(null, TAG_QUOTA);
+ String uuid = request.getVolumeUuid();
+ if (uuid != null) {
+ out.attribute(null, ATTR_UUID, request.getVolumeUuid());
+ }
+ out.attribute(null, ATTR_UID, Integer.toString(request.getUid()));
+ out.attribute(null, ATTR_QUOTA_IN_BYTES, Long.toString(request.getQuota()));
+ out.endTag(null, TAG_QUOTA);
+ }
+ out.endTag(null, CACHE_INFO_TAG);
+ out.endDocument();
+ }
+
+ protected static Pair<Long, List<CacheQuotaHint>> readFromXml(InputStream inputStream)
+ throws XmlPullParserException, IOException {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name());
+
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ }
+
+ if (eventType == XmlPullParser.END_DOCUMENT) {
+ Slog.d(TAG, "No quotas found in quota file.");
+ return null;
+ }
+
+ String tagName = parser.getName();
+ if (!CACHE_INFO_TAG.equals(tagName)) {
+ throw new IllegalStateException("Invalid starting tag.");
+ }
+
+ final List<CacheQuotaHint> quotas = new ArrayList<>();
+ long previousBytes;
+ try {
+ previousBytes = Long.parseLong(parser.getAttributeValue(
+ null, ATTR_PREVIOUS_BYTES));
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException(
+ "Previous bytes formatted incorrectly; aborting quota read.");
+ }
+
+ eventType = parser.next();
+ do {
+ if (eventType == XmlPullParser.START_TAG) {
+ tagName = parser.getName();
+ if (TAG_QUOTA.equals(tagName)) {
+ CacheQuotaHint request = getRequestFromXml(parser);
+ if (request == null) {
+ continue;
+ }
+ quotas.add(request);
+ }
+ }
+ eventType = parser.next();
+ } while (eventType != XmlPullParser.END_DOCUMENT);
+ return new Pair<>(previousBytes, quotas);
+ }
+
+ @VisibleForTesting
+ static CacheQuotaHint getRequestFromXml(XmlPullParser parser) {
+ try {
+ String uuid = parser.getAttributeValue(null, ATTR_UUID);
+ int uid = Integer.parseInt(parser.getAttributeValue(null, ATTR_UID));
+ long bytes = Long.parseLong(parser.getAttributeValue(null, ATTR_QUOTA_IN_BYTES));
+ return new CacheQuotaHint.Builder()
+ .setVolumeUuid(uuid).setUid(uid).setQuota(bytes).build();
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Invalid cache quota request, skipping.");
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 647adbf..4aa013a 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -785,6 +785,16 @@
if (canFreezeBounds()) {
freezeBounds();
}
+
+ // In the process of tearing down before relaunching, the app will
+ // try and clean up it's child surfaces. We need to prevent this from
+ // happening, so we sever the children, transfering their ownership
+ // from the client it-self to the parent surface (owned by us).
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ final WindowState w = mChildren.get(i);
+ w.mWinAnimator.detachChildren();
+ }
+
mPendingRelaunchCount++;
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 6cc2efb..7588b71 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1439,6 +1439,14 @@
mBoundsAnimating = true;
mBoundsAnimatingToFullscreen = toFullscreen;
}
+
+ if (mStackId == PINNED_STACK_ID) {
+ try {
+ mService.mActivityManager.notifyPinnedStackAnimationStarted();
+ } catch (RemoteException e) {
+ // I don't believe you...
+ }
+ }
}
@Override // AnimatesBounds
@@ -1448,6 +1456,7 @@
mBoundsAnimationTarget.setEmpty();
mService.requestTraversal();
}
+
if (mStackId == PINNED_STACK_ID) {
try {
mService.mActivityManager.notifyPinnedStackAnimationEnded();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e339329..eb3a2d1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2199,6 +2199,15 @@
if (mAccessibilityController != null && win.getDisplayId() == DEFAULT_DISPLAY) {
mAccessibilityController.onWindowTransitionLocked(win, transit);
}
+
+ // When we start the exit animation we take the Surface from the client
+ // so it will stop perturbing it. We need to likewise takeaway the SurfaceFlinger
+ // side child surfaces, so they will remain preserved in their current state
+ // (rather than be cleaned up immediately by the app code).
+ SurfaceControl.openTransaction();
+ winAnimator.detachChildren();
+ SurfaceControl.closeTransaction();
+
return focusMayChange;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6d572d7..4806068 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1532,6 +1532,13 @@
return changed;
}
+ // Next up we will notify the client that it's visibility has changed.
+ // We need to prevent it from destroying child surfaces until
+ // the animation has finished.
+ if (!visible && isVisibleNow()) {
+ mWinAnimator.detachChildren();
+ }
+
if (visible != isVisibleNow()) {
if (!runningAppAnimation) {
final AccessibilityController accessibilityController =
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 98598e1..0b1f906 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -566,6 +566,20 @@
if (!mDestroyPreservedSurfaceUponRedraw) {
return;
}
+ if (mSurfaceController != null) {
+ if (mPendingDestroySurface != null) {
+ // If we are preserving a surface but we aren't relaunching that means
+ // we are just doing an in-place switch. In that case any SurfaceFlinger side
+ // child layers need to be reparented to the new surface to make this
+ // transparent to the app.
+ if (mWin.mAppToken == null || mWin.mAppToken.isRelaunching() == false) {
+ SurfaceControl.openTransaction();
+ mPendingDestroySurface.reparentChildrenInTransaction(mSurfaceController);
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
destroyDeferredSurfaceLocked();
mDestroyPreservedSurfaceUponRedraw = false;
}
@@ -1965,4 +1979,11 @@
}
return mForceScaleUntilResize;
}
+
+ void detachChildren() {
+ Slog.i(TAG, "detaching children: " + this);
+ if (mSurfaceController != null) {
+ mSurfaceController.detachChildren();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index f8e7428..f7d3343 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -135,6 +135,20 @@
}
}
+ void reparentChildrenInTransaction(WindowSurfaceController other) {
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, "REPARENT from: " + this + " to: " + other);
+ if ((mSurfaceControl != null) && (other.mSurfaceControl != null)) {
+ mSurfaceControl.reparentChildren(other.getHandle());
+ }
+ }
+
+ void detachChildren() {
+ if (SHOW_TRANSACTIONS) Slog.i(TAG, "SEVER CHILDREN");
+ if (mSurfaceControl != null) {
+ mSurfaceControl.detachChildren();
+ }
+ }
+
void hideInTransaction(String reason) {
if (SHOW_TRANSACTIONS) logSurface("HIDE ( " + reason + " )", null);
mHiddenForOtherReasons = true;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8b94ca0..9c3ecd0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -99,6 +99,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.StringParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -166,6 +167,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
@@ -243,10 +245,14 @@
private static final String TAG_DEFAULT_INPUT_METHOD_SET = "default-ime-set";
+ private static final String TAG_OWNER_INSTALLED_CA_CERT = "owner-installed-ca-cert";
+
private static final String ATTR_ID = "id";
private static final String ATTR_VALUE = "value";
+ private static final String ATTR_ALIAS = "alias";
+
private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
@@ -480,6 +486,7 @@
final ArrayList<ActiveAdmin> mAdminList = new ArrayList<>();
final ArrayList<ComponentName> mRemovingAdmins = new ArrayList<>();
+ // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
final ArraySet<String> mAcceptedCaCertificates = new ArraySet<>();
// This is the list of component allowed to start lock task mode.
@@ -504,6 +511,9 @@
boolean mDefaultInputMethodSet = false;
+ // TODO(b/35385311): Keep track of metadata in TrustedCertificateStore instead.
+ Set<String> mOwnerInstalledCaCerts = new ArraySet<>();
+
// Used for initialization of users created by createAndManageUsers.
boolean mAdminBroadcastPending = false;
PersistableBundle mInitBundle = null;
@@ -518,6 +528,7 @@
final SparseArray<DevicePolicyData> mUserData = new SparseArray<>();
final Handler mHandler;
+ final Handler mBackgroundHandler;
/** Listens on any device, even when mHasFeature == false. */
final BroadcastReceiver mRootCaReceiver = new BroadcastReceiver() {
@@ -619,6 +630,25 @@
handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle);
} else if (Intent.ACTION_MANAGED_PROFILE_ADDED.equals(action)) {
clearWipeProfileNotification();
+ } else if (KeyChain.ACTION_TRUST_STORE_CHANGED.equals(intent.getAction())) {
+ mBackgroundHandler.post(() -> {
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ UserHandle.of(userHandle))) {
+ final List<String> caCerts =
+ keyChainConnection.getService().getUserCaAliases().getList();
+ synchronized (DevicePolicyManagerService.this) {
+ if (getUserData(userHandle).mOwnerInstalledCaCerts
+ .retainAll(caCerts)) {
+ saveSettingsLocked(userHandle);
+ }
+ }
+ } catch (InterruptedException e) {
+ Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
+ Thread.currentThread().interrupt();
+ } catch (RemoteException e) {
+ Slog.w(LOG_TAG, "error talking to IKeyChainService", e);
+ }
+ });
}
}
@@ -1786,6 +1816,7 @@
.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
mIsWatch = mInjector.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ mBackgroundHandler = BackgroundThread.getHandler();
// Broadcast filter for changes to the trusted certificate store. These changes get a
// separate intent filter so we can listen to them even when device_admin is off.
@@ -1807,6 +1838,7 @@
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
+ filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler);
filter = new IntentFilter();
@@ -2580,6 +2612,12 @@
out.endTag(null, TAG_DEFAULT_INPUT_METHOD_SET);
}
+ for (final String cert : policy.mOwnerInstalledCaCerts) {
+ out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+ out.attribute(null, ATTR_ALIAS, cert);
+ out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -2696,6 +2734,7 @@
policy.mAdminList.clear();
policy.mAdminMap.clear();
policy.mAffiliationIds.clear();
+ policy.mOwnerInstalledCaCerts.clear();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -2791,6 +2830,8 @@
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_DEFAULT_INPUT_METHOD_SET.equals(tag)) {
policy.mDefaultInputMethodSet = true;
+ } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
+ policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -4691,17 +4732,15 @@
return false;
}
- final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+ final UserHandle userHandle = UserHandle.of(mInjector.userHandleGetCallingUserId());
final long id = mInjector.binderClearCallingIdentity();
+ String alias = null;
try {
- final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
- try {
- keyChainConnection.getService().installCaCertificate(pemCert);
- return true;
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ userHandle)) {
+ alias = keyChainConnection.getService().installCaCertificate(pemCert);
} catch (RemoteException e) {
Log.e(LOG_TAG, "installCaCertsToKeyChain(): ", e);
- } finally {
- keyChainConnection.close();
}
} catch (InterruptedException e1) {
Log.w(LOG_TAG, "installCaCertsToKeyChain(): ", e1);
@@ -4709,6 +4748,16 @@
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
+ if (alias == null) {
+ Log.w(LOG_TAG, "Problem installing cert");
+ } else {
+ synchronized (this) {
+ final int userId = userHandle.getIdentifier();
+ getUserData(userId).mOwnerInstalledCaCerts.add(alias);
+ saveSettingsLocked(userId);
+ }
+ return true;
+ }
return false;
}
@@ -4722,25 +4771,31 @@
public void uninstallCaCerts(ComponentName admin, String callerPackage, String[] aliases) {
enforceCanManageCaCerts(admin, callerPackage);
- final UserHandle userHandle = new UserHandle(UserHandle.getCallingUserId());
+ final int userId = mInjector.userHandleGetCallingUserId();
+ final UserHandle userHandle = UserHandle.of(userId);
final long id = mInjector.binderClearCallingIdentity();
try {
- final KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, userHandle);
- try {
+ try (final KeyChainConnection keyChainConnection = mInjector.keyChainBindAsUser(
+ userHandle)) {
for (int i = 0 ; i < aliases.length; i++) {
keyChainConnection.getService().deleteCaCertificate(aliases[i]);
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "from CaCertUninstaller: ", e);
- } finally {
- keyChainConnection.close();
+ return;
}
} catch (InterruptedException ie) {
Log.w(LOG_TAG, "CaCertUninstaller: ", ie);
Thread.currentThread().interrupt();
+ return;
} finally {
mInjector.binderRestoreCallingIdentity(id);
}
+ synchronized (this) {
+ if (getUserData(userId).mOwnerInstalledCaCerts.removeAll(Arrays.asList(aliases))) {
+ saveSettingsLocked(userId);
+ }
+ }
}
@Override
@@ -6731,6 +6786,7 @@
}
final DevicePolicyData policyData = getUserData(userId);
policyData.mDefaultInputMethodSet = false;
+ policyData.mOwnerInstalledCaCerts.clear();
saveSettingsLocked(userId);
clearUserPoliciesLocked(userId);
mOwners.removeProfileOwner(userId);
@@ -7127,29 +7183,32 @@
}
private void enforceFullCrossUsersPermission(int userHandle) {
- enforceSystemUserOrPermission(userHandle,
+ enforceSystemUserOrPermissionIfCrossUser(userHandle,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
}
private void enforceCrossUsersPermission(int userHandle) {
- enforceSystemUserOrPermission(userHandle,
+ enforceSystemUserOrPermissionIfCrossUser(userHandle,
android.Manifest.permission.INTERACT_ACROSS_USERS);
}
- private void enforceSystemUserOrPermission(int userHandle, String permission) {
- if (userHandle < 0) {
- throw new IllegalArgumentException("Invalid userId " + userHandle);
- }
- final int callingUid = mInjector.binderGetCallingUid();
- if (userHandle == UserHandle.getUserId(callingUid)) {
- return;
- }
- if (!(isCallerWithSystemUid() || callingUid == Process.ROOT_UID)) {
+ private void enforceSystemUserOrPermission(String permission) {
+ if (!(isCallerWithSystemUid() || mInjector.binderGetCallingUid() == Process.ROOT_UID)) {
mContext.enforceCallingOrSelfPermission(permission,
"Must be system or have " + permission + " permission");
}
}
+ private void enforceSystemUserOrPermissionIfCrossUser(int userHandle, String permission) {
+ if (userHandle < 0) {
+ throw new IllegalArgumentException("Invalid userId " + userHandle);
+ }
+ if (userHandle == mInjector.userHandleGetCallingUserId()) {
+ return;
+ }
+ enforceSystemUserOrPermission(permission);
+ }
+
private void enforceManagedProfile(int userHandle, String message) {
if(!isManagedProfile(userHandle)) {
throw new SecurityException("You can not " + message + " outside a managed profile.");
@@ -7184,6 +7243,21 @@
"Only profile owner, device owner and system may call this method.");
}
+ private void enforceProfileOwnerOrFullCrossUsersPermission(int userId) {
+ if (userId == mInjector.userHandleGetCallingUserId()) {
+ synchronized (this) {
+ if (getActiveAdminWithPolicyForUidLocked(null,
+ DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid())
+ != null) {
+ // Device Owner/Profile Owner may access the user it runs on.
+ return;
+ }
+ }
+ }
+ // Otherwise, INTERACT_ACROSS_USERS_FULL permission, system UID or root UID is required.
+ enforceSystemUserOrPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+
private void ensureCallerPackage(@Nullable String packageName) {
if (packageName == null) {
Preconditions.checkState(isCallerWithSystemUid(),
@@ -10897,4 +10971,14 @@
}
return getUserData(userId).mDefaultInputMethodSet;
}
+
+ @Override
+ public StringParceledListSlice getOwnerInstalledCaCerts(@NonNull UserHandle user) {
+ final int userId = user.getIdentifier();
+ enforceProfileOwnerOrFullCrossUsersPermission(userId);
+ synchronized (this) {
+ return new StringParceledListSlice(
+ new ArrayList<>(getUserData(userId).mOwnerInstalledCaCerts));
+ }
+ }
}
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index ffb0a9e..25c29ee 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -209,6 +209,12 @@
assertEquals(expected.getLightColor(), actual.getLightColor());
}
+ private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
+ assertEquals(expected.getId(), actual.getId());
+ assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getNameResId(), actual.getNameResId());
+ }
+
@Test
public void testFindAfterRankingWithASplitGroup() throws Exception {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -262,8 +268,10 @@
@Test
public void testChannelXml() throws Exception {
int nameResId = 924896;
+ int groupNameResId = 426272;
- NotificationChannelGroup ncg = new NotificationChannelGroup("1", "2");
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", groupNameResId);
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
@@ -278,6 +286,7 @@
channel2.setLightColor(Color.BLUE);
mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
+ mHelper.createNotificationChannelGroup(pkg, uid, ncg2, true);
mHelper.createNotificationChannel(pkg, uid, channel1, true);
mHelper.createNotificationChannel(pkg, uid, channel2, false);
@@ -308,7 +317,9 @@
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
foundNcg = true;
- break;
+ compareGroups(ncg, actual);
+ } else if (ncg2.getId().equals(actual.getId())) {
+ compareGroups(ncg2, actual);
}
}
assertTrue(foundNcg);
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index d7bfc44..f3f68ff 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -38,6 +38,7 @@
import android.content.IntentSender;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutServiceInternal;
+import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.test.InstrumentationTestCase;
@@ -104,21 +105,21 @@
if (otherProvider == null) {
// No other provider found. Ignore this test.
}
- assertFalse(mManager.requestPinAppWidget(otherProvider, null));
+ assertFalse(mManager.requestPinAppWidget(otherProvider, null, null));
}
public void testRequestPinAppWidget() {
ComponentName provider = new ComponentName(mTestContext, DummyAppWidget.class);
// Set up users.
when(mMockShortcutService.requestPinAppWidget(anyString(),
- any(AppWidgetProviderInfo.class), any(IntentSender.class), anyInt()))
+ any(AppWidgetProviderInfo.class), any(Bundle.class), any(IntentSender.class), anyInt()))
.thenReturn(true);
- assertTrue(mManager.requestPinAppWidget(provider, null));
+ assertTrue(mManager.requestPinAppWidget(provider, null, null));
final ArgumentCaptor<AppWidgetProviderInfo> providerCaptor =
ArgumentCaptor.forClass(AppWidgetProviderInfo.class);
verify(mMockShortcutService, times(1)).requestPinAppWidget(anyString(),
- providerCaptor.capture(), eq(null), anyInt());
+ providerCaptor.capture(), any(null), eq(null), anyInt());
assertEquals(provider, providerCaptor.getValue().provider);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 756514b..f797f31 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -49,6 +49,8 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.provider.Settings;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
import android.telephony.TelephonyManager;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
@@ -122,6 +124,34 @@
public DevicePolicyManager dpm;
public DevicePolicyManagerServiceTestable dpms;
+ /*
+ * The CA cert below is the content of cacert.pem as generated by:
+ *
+ * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
+ */
+ private static final String TEST_CA =
+ "-----BEGIN CERTIFICATE-----\n" +
+ "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
+ "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
+ "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
+ "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
+ "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
+ "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
+ "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
+ "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
+ "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
+ "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
+ "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
+ "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
+ "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
+ "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
+ "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
+ "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
+ "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
+ "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
+ "wQ==\n" +
+ "-----END CERTIFICATE-----\n";
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -3916,6 +3946,124 @@
assertFalse(dpm.isDefaultInputMethodSetByOwner(secondUser));
}
+ public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
+ setDeviceOwner();
+
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ verifyCanGetOwnerInstalledCaCerts(admin1);
+ }
+
+ public void testGetOwnerInstalledCaCertsForProfileOwner() throws Exception {
+ setAsProfileOwner(admin1);
+
+ mContext.packageName = admin1.getPackageName();
+ verifyCanGetOwnerInstalledCaCerts(admin1);
+ verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(admin1);
+ }
+
+ public void testGetOwnerInstalledCaCertsForDelegate() throws Exception {
+ setAsProfileOwner(admin1);
+
+ final String delegate = "com.example.delegate";
+ final int delegateUid = setupPackageInPackageManager(delegate, 20988);
+ dpm.setCertInstallerPackage(admin1, delegate);
+
+ mContext.packageName = delegate;
+ mContext.binder.callingUid = delegateUid;
+ verifyCanGetOwnerInstalledCaCerts(null);
+ verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(null);
+ }
+
+ private void verifyCanGetOwnerInstalledCaCerts(ComponentName caller) throws Exception {
+ final UserHandle user = UserHandle.getUserHandleForUid(mContext.binder.callingUid);
+ final int ownerUid = user.equals(UserHandle.SYSTEM) ?
+ DpmMockContext.CALLER_SYSTEM_USER_UID : DpmMockContext.CALLER_UID;
+
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.userContexts.put(user, mContext);
+ when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+ // Install a CA cert.
+ final String alias = "cert";
+ final byte[] caCert = TEST_CA.getBytes();
+ when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+ .thenReturn(alias);
+ assertTrue(dpm.installCaCert(caller, caCert));
+ when(mContext.keyChainConnection.getService().getUserCaAliases())
+ .thenReturn(asSlice(new String[] {alias}));
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Device Owner / Profile Owner can find out which CA certs were installed by itself.
+ final String packageName = mContext.packageName;
+ mContext.packageName = admin1.getPackageName();
+ final long callerIdentity = mContext.binder.clearCallingIdentity();
+ mContext.binder.callingUid = ownerUid;
+ List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertEquals(1, ownerInstalledCaCerts.size());
+ assertTrue(ownerInstalledCaCerts.contains(alias));
+
+ // Restarting the DPMS should not lose information.
+ initializeDpms();
+ assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+
+ // System can find out which CA certs were installed by the Device Owner / Profile Owner.
+ mContext.packageName = "com.android.frameworks.servicestests";
+ mContext.binder.clearCallingIdentity();
+ assertEquals(ownerInstalledCaCerts, dpm.getOwnerInstalledCaCerts(user));
+
+ // Remove the CA cert.
+ mContext.packageName = packageName;
+ mContext.binder.restoreCallingIdentity(callerIdentity);
+ reset(mContext.keyChainConnection.getService());
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Verify that the CA cert is no longer reported as installed by the Device Owner / Profile
+ // Owner.
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = ownerUid;
+ ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertTrue(ownerInstalledCaCerts.isEmpty());
+
+ mContext.packageName = packageName;
+ mContext.binder.restoreCallingIdentity(callerIdentity);
+ }
+
+ private void verifyCantGetOwnerInstalledCaCertsProfileOwnerRemoval(ComponentName caller)
+ throws Exception {
+ final UserHandle user = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
+
+ mContext.applicationInfo = new ApplicationInfo();
+ mContext.userContexts.put(user, mContext);
+ when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+
+ // Install a CA cert.
+ final String alias = "cert";
+ final byte[] caCert = TEST_CA.getBytes();
+ when(mContext.keyChainConnection.getService().installCaCertificate(caCert))
+ .thenReturn(alias);
+ assertTrue(dpm.installCaCert(caller, caCert));
+ when(mContext.keyChainConnection.getService().getUserCaAliases())
+ .thenReturn(asSlice(new String[] {alias}));
+ mContext.injectBroadcast(new Intent(KeyChain.ACTION_TRUST_STORE_CHANGED));
+ flushTasks();
+
+ // Removing the Profile Owner should clear the information which CA certs were installed
+ // by it.
+ mContext.packageName = admin1.getPackageName();
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.clearProfileOwner(admin1);
+ mContext.packageName = "com.android.frameworks.servicestests";
+ mContext.binder.clearCallingIdentity();
+ final List<String> ownerInstalledCaCerts = dpm.getOwnerInstalledCaCerts(user);
+ assertNotNull(ownerInstalledCaCerts);
+ assertTrue(ownerInstalledCaCerts.isEmpty());
+ }
+
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
@@ -3978,4 +4126,31 @@
private static StringParceledListSlice asSlice(String[] s) {
return new StringParceledListSlice(Arrays.asList(s));
}
+
+ private void flushTasks() throws Exception {
+ Boolean tasksFlushed[] = new Boolean[] {false};
+ final Runnable tasksFlushedNotifier = () -> {
+ synchronized (tasksFlushed) {
+ tasksFlushed[0] = true;
+ tasksFlushed.notify();
+ }
+ };
+
+ // Flush main thread handler.
+ dpms.mHandler.post(tasksFlushedNotifier);
+ synchronized (tasksFlushed) {
+ if (!tasksFlushed[0]) {
+ tasksFlushed.wait();
+ }
+ }
+
+ // Flush background thread handler.
+ tasksFlushed[0] = false;
+ dpms.mBackgroundHandler.post(tasksFlushedNotifier);
+ synchronized (tasksFlushed) {
+ if (!tasksFlushed[0]) {
+ tasksFlushed.wait();
+ }
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 258b393..7d017c5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -316,6 +316,41 @@
public ApplicationInfo applicationInfo = null;
+ // We have to keep track of broadcast receivers registered for a given intent ourselves as the
+ // DPM unit tests mock out the package manager and PackageManager.queryBroadcastReceivers() does
+ // not work.
+ private class BroadcastReceiverRegistration {
+ public final BroadcastReceiver receiver;
+ public final IntentFilter filter;
+ public final Handler scheduler;
+
+ public BroadcastReceiverRegistration(BroadcastReceiver receiver, IntentFilter filter,
+ Handler scheduler) {
+ this.receiver = receiver;
+ this.filter = filter;
+ this.scheduler = scheduler;
+ }
+
+ public void sendBroadcastIfApplicable(int userId, Intent intent) {
+ final BroadcastReceiver.PendingResult result = new BroadcastReceiver.PendingResult(
+ 0 /* resultCode */, null /* resultData */, null /* resultExtras */,
+ 0 /* type */, false /* ordered */, false /* sticky */, null /* token */, userId,
+ 0 /* flags */);
+ if (filter.match(null, intent, false, "DpmMockContext") > 0) {
+ if (scheduler != null) {
+ scheduler.post(() -> {
+ receiver.setPendingResult(result);
+ receiver.onReceive(DpmMockContext.this, intent);
+ });
+ } else {
+ receiver.setPendingResult(result);
+ receiver.onReceive(DpmMockContext.this, intent);
+ }
+ }
+ }
+ }
+ private List<BroadcastReceiverRegistration> mBroadcastReceivers = new ArrayList<>();
+
public DpmMockContext(Context context, File dataDir) {
realTestContext = context;
@@ -476,6 +511,13 @@
.thenReturn(isRunning);
}
+ public void injectBroadcast(Intent intent) {
+ final int userId = UserHandle.getUserId(binder.getCallingUid());
+ for (final BroadcastReceiverRegistration receiver : mBroadcastReceivers) {
+ receiver.sendBroadcastIfApplicable(userId, intent);
+ }
+ }
+
@Override
public Resources getResources() {
return resources;
@@ -681,24 +723,28 @@
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, null));
return spiedContext.registerReceiver(receiver, filter);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
return spiedContext.registerReceiver(receiver, filter, broadcastPermission, scheduler);
}
@Override
public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ mBroadcastReceivers.add(new BroadcastReceiverRegistration(receiver, filter, scheduler));
return spiedContext.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
scheduler);
}
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
+ mBroadcastReceivers.removeIf(r -> r.receiver == receiver);
spiedContext.unregisterReceiver(receiver);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
index 26033a3..012024f1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest9.java
@@ -95,7 +95,7 @@
assertExpectException(IllegalStateException.class, "foreground activity", () -> {
mInternal.requestPinAppWidget(CALLING_PACKAGE_1, makeProviderInfo("dummy"),
- /* resultIntent= */ null, USER_P0);
+ null /* extras */, null /* resultIntent= */, USER_P0);
});
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
@@ -111,7 +111,7 @@
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
AppWidgetProviderInfo info = makeProviderInfo("c1");
- assertTrue(mInternal.requestPinAppWidget(CALLING_PACKAGE_1, info,
+ assertTrue(mInternal.requestPinAppWidget(CALLING_PACKAGE_1, info, null,
resultIntent == null ? null : resultIntent.getIntentSender(), USER_P0));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
diff --git a/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
new file mode 100644
index 0000000..1d62e01
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/storage/CacheQuotaStrategyTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 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.server.storage;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.usage.CacheQuotaHint;
+import android.test.AndroidTestCase;
+import android.util.Pair;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class CacheQuotaStrategyTest extends AndroidTestCase {
+ StringWriter mWriter;
+ FastXmlSerializer mOut;
+
+ @Before
+ public void setUp() throws Exception {
+ mWriter = new StringWriter();
+ mOut = new FastXmlSerializer();
+ mOut.setOutput(mWriter);
+ }
+
+ @Test
+ public void testEmptyWrite() throws Exception {
+ CacheQuotaStrategy.saveToXml(mOut, new ArrayList<>(), 0);
+ mOut.flush();
+
+ assertThat(mWriter.toString()).isEqualTo(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"0\" />\n");
+ }
+
+ @Test
+ public void testWriteOneQuota() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(buildCacheQuotaHint("uuid", 0, 100));
+
+ CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+ mOut.flush();
+
+ assertThat(mWriter.toString()).isEqualTo(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"1000\">\n"
+ + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+ + "</cache-info>\n");
+ }
+
+ @Test
+ public void testWriteMultipleQuotas() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(buildCacheQuotaHint("uuid", 0, 100));
+ requests.add(buildCacheQuotaHint("uuid2", 10, 250));
+
+ CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+ mOut.flush();
+
+ assertThat(mWriter.toString()).isEqualTo(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"1000\">\n"
+ + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+ + "<quota uuid=\"uuid2\" uid=\"10\" bytes=\"250\" />\n"
+ + "</cache-info>\n");
+ }
+
+ @Test
+ public void testNullUuidDoesntCauseCrash() throws Exception {
+ ArrayList<CacheQuotaHint> requests = new ArrayList<>();
+ requests.add(buildCacheQuotaHint(null, 0, 100));
+ requests.add(buildCacheQuotaHint(null, 10, 250));
+
+ CacheQuotaStrategy.saveToXml(mOut, requests, 1000);
+ mOut.flush();
+
+ assertThat(mWriter.toString()).isEqualTo(
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n" +
+ "<cache-info previousBytes=\"1000\">\n"
+ + "<quota uid=\"0\" bytes=\"100\" />\n"
+ + "<quota uid=\"10\" bytes=\"250\" />\n"
+ + "</cache-info>\n");
+ }
+
+ @Test
+ public void testReadMultipleQuotas() throws Exception {
+ String input = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<cache-info previousBytes=\"1000\">\n"
+ + "<quota uuid=\"uuid\" uid=\"0\" bytes=\"100\" />\n"
+ + "<quota uuid=\"uuid2\" uid=\"10\" bytes=\"250\" />\n"
+ + "</cache-info>\n";
+
+ Pair<Long, List<CacheQuotaHint>> output =
+ CacheQuotaStrategy.readFromXml(new ByteArrayInputStream(input.getBytes("UTF-8")));
+
+ assertThat(output.first).isEqualTo(1000);
+ assertThat(output.second).containsExactly(buildCacheQuotaHint("uuid", 0, 100),
+ buildCacheQuotaHint("uuid2", 10, 250));
+ }
+
+ private CacheQuotaHint buildCacheQuotaHint(String volumeUuid, int uid, long quota) {
+ return new CacheQuotaHint.Builder()
+ .setVolumeUuid(volumeUuid).setUid(uid).setQuota(quota).build();
+ }
+}
\ No newline at end of file
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index ed1530a..89e68a6 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -55,6 +55,8 @@
import com.android.server.pm.Installer.InstallerException;
import com.android.server.storage.CacheQuotaStrategy;
+import java.io.IOException;
+
public class StorageStatsService extends IStorageStatsManager.Stub {
private static final String TAG = "StorageStatsService";
@@ -97,7 +99,7 @@
invalidateMounts();
mHandler = new H(IoThread.get().getLooper());
- mHandler.sendEmptyMessageDelayed(H.MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+ mHandler.sendEmptyMessageDelayed(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE, DELAY_IN_MILLIS);
mStorage.registerListener(new StorageEventListener() {
@Override
@@ -343,12 +345,14 @@
private class H extends Handler {
private static final int MSG_CHECK_STORAGE_DELTA = 100;
+ private static final int MSG_LOAD_CACHED_QUOTAS_FROM_FILE = 101;
/**
* By only triggering a re-calculation after the storage has changed sizes, we can avoid
* recalculating quotas too often. Minimum change delta defines the percentage of change
* we need to see before we recalculate.
*/
private static final double MINIMUM_CHANGE_DELTA = 0.05;
+ private static final int UNSET = -1;
private static final boolean DEBUG = false;
private final StatFs mStats;
@@ -361,7 +365,6 @@
mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath());
mPreviousBytes = mStats.getFreeBytes();
mMinimumThresholdBytes = mStats.getTotalBytes() * MINIMUM_CHANGE_DELTA;
- // TODO: Load cache quotas from a file to avoid re-doing work.
}
public void handleMessage(Message msg) {
@@ -378,7 +381,26 @@
long bytesDelta = Math.abs(mPreviousBytes - mStats.getFreeBytes());
if (bytesDelta > mMinimumThresholdBytes) {
mPreviousBytes = mStats.getFreeBytes();
- recalculateQuotas();
+ recalculateQuotas(getInitializedStrategy());
+ }
+ sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
+ break;
+ }
+ case MSG_LOAD_CACHED_QUOTAS_FROM_FILE: {
+ CacheQuotaStrategy strategy = getInitializedStrategy();
+ mPreviousBytes = UNSET;
+ try {
+ mPreviousBytes = strategy.setupQuotasFromFile();
+ } catch (IOException e) {
+ Slog.e(TAG, "An error occurred while reading the cache quota file.", e);
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Cache quota XML file is malformed?", e);
+ }
+
+ // If errors occurred getting the quotas from disk, let's re-calc them.
+ if (mPreviousBytes < 0) {
+ mPreviousBytes = mStats.getFreeBytes();
+ recalculateQuotas(strategy);
}
sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_IN_MILLIS);
break;
@@ -391,17 +413,18 @@
}
}
- private void recalculateQuotas() {
+ private void recalculateQuotas(CacheQuotaStrategy strategy) {
if (DEBUG) {
Slog.v(TAG, ">>> recalculating quotas ");
}
+ strategy.recalculateQuotas();
+ }
+
+ private CacheQuotaStrategy getInitializedStrategy() {
UsageStatsManagerInternal usageStatsManager =
LocalServices.getService(UsageStatsManagerInternal.class);
- CacheQuotaStrategy strategy = new CacheQuotaStrategy(
- mContext, usageStatsManager, mInstaller);
- // TODO: Save cache quotas to an XML file.
- strategy.recalculateQuotas();
+ return new CacheQuotaStrategy(mContext, usageStatsManager, mInstaller);
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index ab988b2..3c7ee43 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -98,7 +98,7 @@
static final String TAG = "UsageStatsService";
public static final boolean ENABLE_TIME_CHANGE_CORRECTION
- = SystemProperties.getBoolean("debug.time_change_correction", true);
+ = SystemProperties.getBoolean("persist.debug.time_correction", true);
static final boolean DEBUG = false; // Never submit with true
static final boolean COMPRESS_TIME = false;
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
new file mode 100644
index 0000000..8590482
--- /dev/null
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -0,0 +1,1707 @@
+/*
+ * Copyright (C) 2008 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.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.telephony.IMms;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsRawData;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * TODO(code review): Curious question... Why are a lot of these
+ * methods not declared as static, since they do not seem to require
+ * any local object state? Presumably this cannot be changed without
+ * interfering with the API...
+ */
+
+/**
+ * Manages SMS operations such as sending data, text, and pdu SMS messages.
+ * Get this object by calling the static method {@link #getDefault()}.
+ *
+ * <p>For information about how to behave as the default SMS app on Android 4.4 (API level 19)
+ * and higher, see {@link android.provider.Telephony}.
+ */
+public final class SmsManager {
+ private static final String TAG = "SmsManager";
+ /**
+ * A psuedo-subId that represents the default subId at any given time. The actual subId it
+ * represents changes as the default subId is changed.
+ */
+ private static final int DEFAULT_SUBSCRIPTION_ID = -1002;
+
+ /** Singleton object constructed during class initialization. */
+ private static final SmsManager sInstance = new SmsManager(DEFAULT_SUBSCRIPTION_ID);
+ private static final Object sLockObject = new Object();
+
+ /** @hide */
+ public static final int CELL_BROADCAST_RAN_TYPE_GSM = 0;
+ /** @hide */
+ public static final int CELL_BROADCAST_RAN_TYPE_CDMA = 1;
+
+ /** SMS record length from TS 51.011 10.5.3
+ * @hide
+ */
+ public static final int SMS_RECORD_LENGTH = 176;
+
+ /** SMS record length from C.S0023 3.4.27
+ * @hide
+ */
+ public static final int CDMA_SMS_RECORD_LENGTH = 255;
+
+ private static final Map<Integer, SmsManager> sSubInstances =
+ new ArrayMap<Integer, SmsManager>();
+
+ /** A concrete subscription id, or the pseudo DEFAULT_SUBSCRIPTION_ID */
+ private int mSubId;
+
+ /*
+ * Key for the various carrier-dependent configuration values.
+ * Some of the values are used by the system in processing SMS or MMS messages. Others
+ * are provided for the convenience of SMS applications.
+ */
+
+ /**
+ * Whether to append transaction id to MMS WAP Push M-Notification.ind's content location URI
+ * when constructing the download URL of a new MMS (boolean type)
+ */
+ public static final String MMS_CONFIG_APPEND_TRANSACTION_ID =
+ CarrierConfigManager.KEY_MMS_APPEND_TRANSACTION_ID_BOOL;
+ /**
+ * Whether MMS is enabled for the current carrier (boolean type)
+ */
+ public static final String
+ MMS_CONFIG_MMS_ENABLED = CarrierConfigManager.KEY_MMS_MMS_ENABLED_BOOL;
+ /**
+ * Whether group MMS is enabled for the current carrier (boolean type)
+ */
+ public static final String
+ MMS_CONFIG_GROUP_MMS_ENABLED = CarrierConfigManager.KEY_MMS_GROUP_MMS_ENABLED_BOOL;
+ /**
+ * If this is enabled, M-NotifyResp.ind should be sent to the WAP Push content location instead
+ * of the default MMSC (boolean type)
+ */
+ public static final String MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED =
+ CarrierConfigManager.KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL;
+ /**
+ * Whether alias is enabled (boolean type)
+ */
+ public static final String
+ MMS_CONFIG_ALIAS_ENABLED = CarrierConfigManager.KEY_MMS_ALIAS_ENABLED_BOOL;
+ /**
+ * Whether audio is allowed to be attached for MMS messages (boolean type)
+ */
+ public static final String
+ MMS_CONFIG_ALLOW_ATTACH_AUDIO = CarrierConfigManager.KEY_MMS_ALLOW_ATTACH_AUDIO_BOOL;
+ /**
+ * Whether multipart SMS is enabled (boolean type)
+ */
+ public static final String MMS_CONFIG_MULTIPART_SMS_ENABLED =
+ CarrierConfigManager.KEY_MMS_MULTIPART_SMS_ENABLED_BOOL;
+ /**
+ * Whether SMS delivery report is enabled (boolean type)
+ */
+ public static final String MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED =
+ CarrierConfigManager.KEY_MMS_SMS_DELIVERY_REPORT_ENABLED_BOOL;
+ /**
+ * Whether content-disposition field should be expected in an MMS PDU (boolean type)
+ */
+ public static final String MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION =
+ CarrierConfigManager.KEY_MMS_SUPPORT_MMS_CONTENT_DISPOSITION_BOOL;
+ /**
+ * Whether multipart SMS should be sent as separate messages
+ */
+ public static final String MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES =
+ CarrierConfigManager.KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL;
+ /**
+ * Whether MMS read report is enabled (boolean type)
+ */
+ public static final String MMS_CONFIG_MMS_READ_REPORT_ENABLED =
+ CarrierConfigManager.KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL;
+ /**
+ * Whether MMS delivery report is enabled (boolean type)
+ */
+ public static final String MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED =
+ CarrierConfigManager.KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL;
+ /**
+ * Max MMS message size in bytes (int type)
+ */
+ public static final String
+ MMS_CONFIG_MAX_MESSAGE_SIZE = CarrierConfigManager.KEY_MMS_MAX_MESSAGE_SIZE_INT;
+ /**
+ * Max MMS image width (int type)
+ */
+ public static final String
+ MMS_CONFIG_MAX_IMAGE_WIDTH = CarrierConfigManager.KEY_MMS_MAX_IMAGE_WIDTH_INT;
+ /**
+ * Max MMS image height (int type)
+ */
+ public static final String
+ MMS_CONFIG_MAX_IMAGE_HEIGHT = CarrierConfigManager.KEY_MMS_MAX_IMAGE_HEIGHT_INT;
+ /**
+ * Limit of recipients of MMS messages (int type)
+ */
+ public static final String
+ MMS_CONFIG_RECIPIENT_LIMIT = CarrierConfigManager.KEY_MMS_RECIPIENT_LIMIT_INT;
+ /**
+ * Min alias character count (int type)
+ */
+ public static final String
+ MMS_CONFIG_ALIAS_MIN_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MIN_CHARS_INT;
+ /**
+ * Max alias character count (int type)
+ */
+ public static final String
+ MMS_CONFIG_ALIAS_MAX_CHARS = CarrierConfigManager.KEY_MMS_ALIAS_MAX_CHARS_INT;
+ /**
+ * When the number of parts of a multipart SMS reaches this threshold, it should be converted
+ * into an MMS (int type)
+ */
+ public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD =
+ CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT;
+ /**
+ * Some carriers require SMS to be converted into MMS when text length reaches this threshold
+ * (int type)
+ */
+ public static final String MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD =
+ CarrierConfigManager.KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT;
+ /**
+ * Max message text size (int type)
+ */
+ public static final String MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE =
+ CarrierConfigManager.KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT;
+ /**
+ * Max message subject length (int type)
+ */
+ public static final String
+ MMS_CONFIG_SUBJECT_MAX_LENGTH = CarrierConfigManager.KEY_MMS_SUBJECT_MAX_LENGTH_INT;
+ /**
+ * MMS HTTP socket timeout in milliseconds (int type)
+ */
+ public static final String
+ MMS_CONFIG_HTTP_SOCKET_TIMEOUT = CarrierConfigManager.KEY_MMS_HTTP_SOCKET_TIMEOUT_INT;
+ /**
+ * The name of the UA Prof URL HTTP header for MMS HTTP request (String type)
+ */
+ public static final String
+ MMS_CONFIG_UA_PROF_TAG_NAME = CarrierConfigManager.KEY_MMS_UA_PROF_TAG_NAME_STRING;
+ /**
+ * The User-Agent header value for MMS HTTP request (String type)
+ */
+ public static final String
+ MMS_CONFIG_USER_AGENT = CarrierConfigManager.KEY_MMS_USER_AGENT_STRING;
+ /**
+ * The UA Profile URL header value for MMS HTTP request (String type)
+ */
+ public static final String
+ MMS_CONFIG_UA_PROF_URL = CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING;
+ /**
+ * A list of HTTP headers to add to MMS HTTP request, separated by "|" (String type)
+ */
+ public static final String
+ MMS_CONFIG_HTTP_PARAMS = CarrierConfigManager.KEY_MMS_HTTP_PARAMS_STRING;
+ /**
+ * Email gateway number (String type)
+ */
+ public static final String MMS_CONFIG_EMAIL_GATEWAY_NUMBER =
+ CarrierConfigManager.KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING;
+ /**
+ * The suffix to append to the NAI header value for MMS HTTP request (String type)
+ */
+ public static final String
+ MMS_CONFIG_NAI_SUFFIX = CarrierConfigManager.KEY_MMS_NAI_SUFFIX_STRING;
+ /**
+ * If true, show the cell broadcast (amber alert) in the SMS settings. Some carriers don't want
+ * this shown. (Boolean type)
+ */
+ public static final String MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS =
+ CarrierConfigManager.KEY_MMS_SHOW_CELL_BROADCAST_APP_LINKS_BOOL;
+ /**
+ * Whether the carrier MMSC supports charset field in Content-Type header. If this is false,
+ * then we don't add "charset" to "Content-Type"
+ */
+ public static final String MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER =
+ CarrierConfigManager.KEY_MMS_SUPPORT_HTTP_CHARSET_HEADER_BOOL;
+ /**
+ * If true, add "Connection: close" header to MMS HTTP requests so the connection
+ * is immediately closed (disabling keep-alive). (Boolean type)
+ * @hide
+ */
+ public static final String MMS_CONFIG_CLOSE_CONNECTION =
+ CarrierConfigManager.KEY_MMS_CLOSE_CONNECTION_BOOL;
+
+ /*
+ * Forwarded constants from SimDialogActivity.
+ */
+ private static String DIALOG_TYPE_KEY = "dialog_type";
+ private static final int SMS_PICK = 2;
+
+ /**
+ * Send a text based SMS.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+ * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+ *
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param text the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or text are empty
+ */
+ public void sendTextMessage(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ true /* persistMessage*/);
+ }
+
+ private void sendTextMessageInternal(String destinationAddress, String scAddress,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+ boolean persistMessage) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
+ destinationAddress,
+ scAddress, text, sentIntent, deliveryIntent,
+ persistMessage);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
+ * @hide
+ */
+ @SystemApi
+ public void sendTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
+ false /* persistMessage */);
+ }
+
+ /**
+ * A variant of {@link SmsManager#sendTextMessage} that allows self to be the caller. This is
+ * for internal use only.
+ *
+ * @param persistMessage whether to persist the sent message in the SMS app. the caller must be
+ * the Phone process if set to false.
+ *
+ * @hide
+ */
+ public void sendTextMessageWithSelfPermissions(
+ String destinationAddress, String scAddress, String text,
+ PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (TextUtils.isEmpty(text)) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendTextForSubscriberWithSelfPermissions(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ destinationAddress,
+ scAddress, text, sentIntent, deliveryIntent, persistMessage);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Inject an SMS PDU into the android application framework.
+ *
+ * The caller should have carrier privileges.
+ * @see android.telephony.TelephonyManager#hasCarrierPrivileges
+ *
+ * @param pdu is the byte array of pdu to be injected into android application framework
+ * @param format is the format of SMS pdu (3gpp or 3gpp2)
+ * @param receivedIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully received by the
+ * android application framework, or failed. This intent is broadcasted at
+ * the same time an SMS received from radio is acknowledged back.
+ * The result code will be <code>RESULT_SMS_HANDLED</code> for success, or
+ * <code>RESULT_SMS_GENERIC_ERROR</code> for error.
+ *
+ * @throws IllegalArgumentException if format is not one of 3gpp and 3gpp2.
+ */
+ public void injectSmsPdu(byte[] pdu, String format, PendingIntent receivedIntent) {
+ if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
+ // Format must be either 3gpp or 3gpp2.
+ throw new IllegalArgumentException(
+ "Invalid pdu format. format must be either 3gpp or 3gpp2");
+ }
+ try {
+ ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ if (iccISms != null) {
+ iccISms.injectSmsPduForSubscriber(
+ getSubscriptionId(), pdu, format, receivedIntent);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Divide a message text into several fragments, none bigger than
+ * the maximum SMS message size.
+ *
+ * @param text the original message. Must not be null.
+ * @return an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ *
+ * @throws IllegalArgumentException if text is null
+ */
+ public ArrayList<String> divideMessage(String text) {
+ if (null == text) {
+ throw new IllegalArgumentException("text is null");
+ }
+ return SmsMessage.fragmentText(text);
+ }
+
+ /**
+ * Send a multi-part text based SMS. The callee should have already
+ * divided the message into correctly sized parts by calling
+ * <code>divideMessage</code>.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * <p class="note"><strong>Note:</strong> Beginning with Android 4.4 (API level 19), if
+ * <em>and only if</em> an app is not selected as the default SMS app, the system automatically
+ * writes messages sent using this method to the SMS Provider (the default SMS app is always
+ * responsible for writing its sent messages to the SMS Provider). For information about
+ * how to behave as the default SMS app, see {@link android.provider.Telephony}.</p>
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param parts an <code>ArrayList</code> of strings that, in order,
+ * comprise the original message
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ */
+ public void sendMultipartTextMessage(
+ String destinationAddress, String scAddress, ArrayList<String> parts,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, true /* persistMessage*/);
+ }
+
+ private void sendMultipartTextMessageInternal(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+ boolean persistMessage) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+ if (parts == null || parts.size() < 1) {
+ throw new IllegalArgumentException("Invalid message body");
+ }
+
+ if (parts.size() > 1) {
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendMultipartTextForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ destinationAddress, scAddress, parts,
+ sentIntents, deliveryIntents, persistMessage);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ } else {
+ PendingIntent sentIntent = null;
+ PendingIntent deliveryIntent = null;
+ if (sentIntents != null && sentIntents.size() > 0) {
+ sentIntent = sentIntents.get(0);
+ }
+ if (deliveryIntents != null && deliveryIntents.size() > 0) {
+ deliveryIntent = deliveryIntents.get(0);
+ }
+ sendTextMessage(destinationAddress, scAddress, parts.get(0),
+ sentIntent, deliveryIntent);
+ }
+ }
+
+ /**
+ * Send a multi-part text based SMS without writing it into the SMS Provider.
+ *
+ * <p>Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or the calling app has carrier
+ * privileges.
+ * </p>
+ *
+ * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList)
+ * @hide
+ **/
+ @SystemApi
+ public void sendMultipartTextMessageWithoutPersisting(
+ String destinationAddress, String scAddress, List<String> parts,
+ List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
+ sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+ deliveryIntents, false /* persistMessage*/);
+ }
+
+ /**
+ * Send a data based SMS to a specific application port.
+ *
+ * <p class="note"><strong>Note:</strong> Using this method requires that your app has the
+ * {@link android.Manifest.permission#SEND_SMS} permission.</p>
+ *
+ * @param destinationAddress the address to send the message to
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param destinationPort the port to deliver the message to
+ * @param data the body of the message to send
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if destinationAddress or data are empty
+ */
+ public void sendDataMessage(
+ String destinationAddress, String scAddress, short destinationPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (data == null || data.length == 0) {
+ throw new IllegalArgumentException("Invalid message data");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendDataForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
+ destinationAddress, scAddress, destinationPort & 0xFFFF,
+ data, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * A variant of {@link SmsManager#sendDataMessage} that allows self to be the caller. This is
+ * for internal use only.
+ *
+ * @hide
+ */
+ public void sendDataMessageWithSelfPermissions(
+ String destinationAddress, String scAddress, short destinationPort,
+ byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ if (TextUtils.isEmpty(destinationAddress)) {
+ throw new IllegalArgumentException("Invalid destinationAddress");
+ }
+
+ if (data == null || data.length == 0) {
+ throw new IllegalArgumentException("Invalid message data");
+ }
+
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendDataForSubscriberWithSelfPermissions(getSubscriptionId(),
+ ActivityThread.currentPackageName(), destinationAddress, scAddress,
+ destinationPort & 0xFFFF, data, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+
+
+ /**
+ * Get the SmsManager associated with the default subscription id. The instance will always be
+ * associated with the default subscription id, even if the default subscription id is changed.
+ *
+ * @return the SmsManager associated with the default subscription id
+ */
+ public static SmsManager getDefault() {
+ return sInstance;
+ }
+
+ /**
+ * Get the the instance of the SmsManager associated with a particular subscription id
+ *
+ * @param subId an SMS subscription id, typically accessed using
+ * {@link android.telephony.SubscriptionManager}
+ * @return the instance of the SmsManager associated with subId
+ */
+ public static SmsManager getSmsManagerForSubscriptionId(int subId) {
+ // TODO(shri): Add javadoc link once SubscriptionManager is made public api
+ synchronized(sLockObject) {
+ SmsManager smsManager = sSubInstances.get(subId);
+ if (smsManager == null) {
+ smsManager = new SmsManager(subId);
+ sSubInstances.put(subId, smsManager);
+ }
+ return smsManager;
+ }
+ }
+
+ private SmsManager(int subId) {
+ mSubId = subId;
+ }
+
+ /**
+ * Get the associated subscription id. If the instance was returned by {@link #getDefault()},
+ * then this method may return different values at different points in time (if the user
+ * changes the default subscription id). It will return < 0 if the default subscription id
+ * cannot be determined.
+ *
+ * Additionally, to support legacy applications that are not multi-SIM aware,
+ * if the following are true:
+ * - We are using a multi-SIM device
+ * - A default SMS SIM has not been selected
+ * - At least one SIM subscription is available
+ * then ask the user to set the default SMS SIM.
+ *
+ * @return associated subscription id
+ */
+ public int getSubscriptionId() {
+ final int subId = (mSubId == DEFAULT_SUBSCRIPTION_ID)
+ ? getDefaultSmsSubscriptionId() : mSubId;
+ boolean isSmsSimPickActivityNeeded = false;
+ final Context context = ActivityThread.currentApplication().getApplicationContext();
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ isSmsSimPickActivityNeeded = iccISms.isSmsSimPickActivityNeeded(subId);
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Exception in getSubscriptionId");
+ }
+
+ if (isSmsSimPickActivityNeeded) {
+ Log.d(TAG, "getSubscriptionId isSmsSimPickActivityNeeded is true");
+ // ask the user for a default SMS SIM.
+ Intent intent = new Intent();
+ intent.setClassName("com.android.settings",
+ "com.android.settings.sim.SimDialogActivity");
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(DIALOG_TYPE_KEY, SMS_PICK);
+ try {
+ context.startActivity(intent);
+ } catch (ActivityNotFoundException anfe) {
+ // If Settings is not installed, only log the error as we do not want to break
+ // legacy applications.
+ Log.e(TAG, "Unable to launch Settings application.");
+ }
+ }
+
+ return subId;
+ }
+
+ /**
+ * Returns the ISms service, or throws an UnsupportedOperationException if
+ * the service does not exist.
+ */
+ private static ISms getISmsServiceOrThrow() {
+ ISms iccISms = getISmsService();
+ if (iccISms == null) {
+ throw new UnsupportedOperationException("Sms is not supported");
+ }
+ return iccISms;
+ }
+
+ private static ISms getISmsService() {
+ return ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ }
+
+ /**
+ * Copy a raw SMS PDU to the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param smsc the SMSC for this message, or NULL for the default SMSC
+ * @param pdu the raw PDU to store
+ * @param status message status (STATUS_ON_ICC_READ, STATUS_ON_ICC_UNREAD,
+ * STATUS_ON_ICC_SENT, STATUS_ON_ICC_UNSENT)
+ * @return true for success
+ *
+ * @throws IllegalArgumentException if pdu is NULL
+ * {@hide}
+ */
+ public boolean copyMessageToIcc(byte[] smsc, byte[] pdu,int status) {
+ boolean success = false;
+
+ if (null == pdu) {
+ throw new IllegalArgumentException("pdu is NULL");
+ }
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.copyMessageToIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ status, pdu, smsc);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Delete the specified message from the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param messageIndex is the record index of the message on ICC
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean
+ deleteMessageFromIcc(int messageIndex) {
+ boolean success = false;
+ byte[] pdu = new byte[SMS_RECORD_LENGTH-1];
+ Arrays.fill(pdu, (byte)0xff);
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ messageIndex, STATUS_ON_ICC_FREE, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Update the specified message on the ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @param messageIndex record index of message to update
+ * @param newStatus new message status (STATUS_ON_ICC_READ,
+ * STATUS_ON_ICC_UNREAD, STATUS_ON_ICC_SENT,
+ * STATUS_ON_ICC_UNSENT, STATUS_ON_ICC_FREE)
+ * @param pdu the raw PDU to store
+ * @return true for success
+ *
+ * {@hide}
+ */
+ public boolean updateMessageOnIcc(int messageIndex, int newStatus, byte[] pdu) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
+ ActivityThread.currentPackageName(),
+ messageIndex, newStatus, pdu);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Retrieves all messages currently stored on ICC.
+ * ICC (Integrated Circuit Card) is the card of the device.
+ * For example, this can be the SIM or USIM for GSM.
+ *
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects
+ *
+ * {@hide}
+ */
+ public ArrayList<SmsMessage> getAllMessagesFromIcc() {
+ List<SmsRawData> records = null;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ records = iccISms.getAllMessagesFromIccEfForSubscriber(
+ getSubscriptionId(),
+ ActivityThread.currentPackageName());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return createMessageListFromRawRecords(records);
+ }
+
+ /**
+ * Enable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier and RAN type. The RAN type specify this message ID
+ * belong to 3GPP (GSM) or 3GPP2(CDMA).Note that if two different clients
+ * enable the same message identifier, they must both disable it for the device to stop
+ * receiving those messages. All received messages will be broadcast in an
+ * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param ranType as defined in class SmsManager, the value can be one of these:
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+ * @return true if successful, false otherwise
+ * @see #disableCellBroadcast(int, int)
+ *
+ * {@hide}
+ */
+ public boolean enableCellBroadcast(int messageIdentifier, int ranType) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.enableCellBroadcastForSubscriber(
+ getSubscriptionId(), messageIdentifier, ranType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Disable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier and RAN type. The RAN type specify this message ID
+ * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients
+ * enable the same message identifier, they must both disable it for the
+ * device to stop receiving those messages.
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param messageIdentifier Message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param ranType as defined in class SmsManager, the value can be one of these:
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+ * @return true if successful, false otherwise
+ *
+ * @see #enableCellBroadcast(int, int)
+ *
+ * {@hide}
+ */
+ public boolean disableCellBroadcast(int messageIdentifier, int ranType) {
+ boolean success = false;
+
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.disableCellBroadcastForSubscriber(
+ getSubscriptionId(), messageIdentifier, ranType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Enable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier range and RAN type. The RAN type specify this message ID
+ * belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different clients enable
+ * the same message identifier, they must both disable it for the device to stop
+ * receiving those messages. All received messages will be broadcast in an
+ * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED".
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param ranType as defined in class SmsManager, the value can be one of these:
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+ * @return true if successful, false otherwise
+ * @see #disableCellBroadcastRange(int, int, int)
+ *
+ * @throws IllegalArgumentException if endMessageId < startMessageId
+ * {@hide}
+ */
+ public boolean enableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+ boolean success = false;
+
+ if (endMessageId < startMessageId) {
+ throw new IllegalArgumentException("endMessageId < startMessageId");
+ }
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.enableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+ startMessageId, endMessageId, ranType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Disable reception of cell broadcast (SMS-CB) messages with the given
+ * message identifier range and RAN type. The RAN type specify this message
+ * ID range belong to 3GPP (GSM) or 3GPP2(CDMA). Note that if two different
+ * clients enable the same message identifier, they must both disable it for
+ * the device to stop receiving those messages.
+ * Note: This call is blocking, callers may want to avoid calling it from
+ * the main thread of an application.
+ *
+ * @param startMessageId first message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param endMessageId last message identifier as specified in TS 23.041 (3GPP)
+ * or C.R1001-G (3GPP2)
+ * @param ranType as defined in class SmsManager, the value can be one of these:
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_GSM
+ * android.telephony.SmsMessage.CELL_BROADCAST_RAN_TYPE_CDMA
+ * @return true if successful, false otherwise
+ *
+ * @see #enableCellBroadcastRange(int, int, int)
+ *
+ * @throws IllegalArgumentException if endMessageId < startMessageId
+ * {@hide}
+ */
+ public boolean disableCellBroadcastRange(int startMessageId, int endMessageId, int ranType) {
+ boolean success = false;
+
+ if (endMessageId < startMessageId) {
+ throw new IllegalArgumentException("endMessageId < startMessageId");
+ }
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ success = iccISms.disableCellBroadcastRangeForSubscriber(getSubscriptionId(),
+ startMessageId, endMessageId, ranType);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return success;
+ }
+
+ /**
+ * Create a list of <code>SmsMessage</code>s from a list of RawSmsData
+ * records returned by <code>getAllMessagesFromIcc()</code>
+ *
+ * @param records SMS EF records, returned by
+ * <code>getAllMessagesFromIcc</code>
+ * @return <code>ArrayList</code> of <code>SmsMessage</code> objects.
+ */
+ private static ArrayList<SmsMessage> createMessageListFromRawRecords(List<SmsRawData> records) {
+ ArrayList<SmsMessage> messages = new ArrayList<SmsMessage>();
+ if (records != null) {
+ int count = records.size();
+ for (int i = 0; i < count; i++) {
+ SmsRawData data = records.get(i);
+ // List contains all records, including "free" records (null)
+ if (data != null) {
+ SmsMessage sms = SmsMessage.createFromEfRecord(i+1, data.getBytes());
+ if (sms != null) {
+ messages.add(sms);
+ }
+ }
+ }
+ }
+ return messages;
+ }
+
+ /**
+ * SMS over IMS is supported if IMS is registered and SMS is supported
+ * on IMS.
+ *
+ * @return true if SMS over IMS is supported, false otherwise
+ *
+ * @see #getImsSmsFormat()
+ *
+ * @hide
+ */
+ public boolean isImsSmsSupported() {
+ boolean boSupported = false;
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ boSupported = iccISms.isImsSmsSupportedForSubscriber(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return boSupported;
+ }
+
+ /**
+ * Gets SMS format supported on IMS. SMS over IMS format is
+ * either 3GPP or 3GPP2.
+ *
+ * @return SmsMessage.FORMAT_3GPP,
+ * SmsMessage.FORMAT_3GPP2
+ * or SmsMessage.FORMAT_UNKNOWN
+ *
+ * @see #isImsSmsSupported()
+ *
+ * @hide
+ */
+ public String getImsSmsFormat() {
+ String format = com.android.internal.telephony.SmsConstants.FORMAT_UNKNOWN;
+ try {
+ ISms iccISms = getISmsService();
+ if (iccISms != null) {
+ format = iccISms.getImsSmsFormatForSubscriber(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return format;
+ }
+
+ /**
+ * Get default sms subscription id
+ *
+ * @return the default SMS subscription id
+ */
+ public static int getDefaultSmsSubscriptionId() {
+ ISms iccISms = null;
+ try {
+ iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ return iccISms.getPreferredSmsSubscription();
+ } catch (RemoteException ex) {
+ return -1;
+ } catch (NullPointerException ex) {
+ return -1;
+ }
+ }
+
+ /**
+ * Get SMS prompt property, enabled or not
+ *
+ * @return true if enabled, false otherwise
+ * @hide
+ */
+ public boolean isSMSPromptEnabled() {
+ ISms iccISms = null;
+ try {
+ iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ return iccISms.isSMSPromptEnabled();
+ } catch (RemoteException ex) {
+ return false;
+ } catch (NullPointerException ex) {
+ return false;
+ }
+ }
+
+ // see SmsMessage.getStatusOnIcc
+
+ /** Free space (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_FREE = 0;
+
+ /** Received and read (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_READ = 1;
+
+ /** Received and unread (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_UNREAD = 3;
+
+ /** Stored and sent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_SENT = 5;
+
+ /** Stored and unsent (TS 51.011 10.5.3 / 3GPP2 C.S0023 3.4.27). */
+ static public final int STATUS_ON_ICC_UNSENT = 7;
+
+ // SMS send failure result codes
+
+ /** Generic failure cause */
+ static public final int RESULT_ERROR_GENERIC_FAILURE = 1;
+ /** Failed because radio was explicitly turned off */
+ static public final int RESULT_ERROR_RADIO_OFF = 2;
+ /** Failed because no pdu provided */
+ static public final int RESULT_ERROR_NULL_PDU = 3;
+ /** Failed because service is currently unavailable */
+ static public final int RESULT_ERROR_NO_SERVICE = 4;
+ /** Failed because we reached the sending queue limit. {@hide} */
+ static public final int RESULT_ERROR_LIMIT_EXCEEDED = 5;
+ /** Failed because FDN is enabled. {@hide} */
+ static public final int RESULT_ERROR_FDN_CHECK_FAILURE = 6;
+
+ static private final String PHONE_PACKAGE_NAME = "com.android.phone";
+
+ /**
+ * Send an MMS message
+ *
+ * @param context application context
+ * @param contentUri the content Uri from which the message pdu will be read
+ * @param locationUrl the optional location url where message should be sent to
+ * @param configOverrides the carrier-specific messaging configuration values to override for
+ * sending the message.
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed
+ * @throws IllegalArgumentException if contentUri is empty
+ */
+ public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
+ Bundle configOverrides, PendingIntent sentIntent) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms == null) {
+ return;
+ }
+
+ iMms.sendMessage(getSubscriptionId(), ActivityThread.currentPackageName(), contentUri,
+ locationUrl, configOverrides, sentIntent);
+ } catch (RemoteException e) {
+ // Ignore it
+ }
+ }
+
+ /**
+ * Download an MMS message from carrier by a given location URL
+ *
+ * @param context application context
+ * @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
+ * from the MMS WAP push notification
+ * @param contentUri the content uri to which the downloaded pdu will be written
+ * @param configOverrides the carrier-specific messaging configuration values to override for
+ * downloading the message.
+ * @param downloadedIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is downloaded, or the download is failed
+ * @throws IllegalArgumentException if locationUrl or contentUri is empty
+ */
+ public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
+ Bundle configOverrides, PendingIntent downloadedIntent) {
+ if (TextUtils.isEmpty(locationUrl)) {
+ throw new IllegalArgumentException("Empty MMS location URL");
+ }
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms == null) {
+ return;
+ }
+ iMms.downloadMessage(
+ getSubscriptionId(), ActivityThread.currentPackageName(), locationUrl,
+ contentUri, configOverrides, downloadedIntent);
+ } catch (RemoteException e) {
+ // Ignore it
+ }
+ }
+
+ // MMS send/download failure result codes
+ public static final int MMS_ERROR_UNSPECIFIED = 1;
+ public static final int MMS_ERROR_INVALID_APN = 2;
+ public static final int MMS_ERROR_UNABLE_CONNECT_MMS = 3;
+ public static final int MMS_ERROR_HTTP_FAILURE = 4;
+ public static final int MMS_ERROR_IO_ERROR = 5;
+ public static final int MMS_ERROR_RETRY = 6;
+ public static final int MMS_ERROR_CONFIGURATION_ERROR = 7;
+ public static final int MMS_ERROR_NO_DATA_NETWORK = 8;
+
+ /** Intent extra name for MMS sending result data in byte array type */
+ public static final String EXTRA_MMS_DATA = "android.telephony.extra.MMS_DATA";
+ /** Intent extra name for HTTP status code for MMS HTTP failure in integer type */
+ public static final String EXTRA_MMS_HTTP_STATUS = "android.telephony.extra.MMS_HTTP_STATUS";
+
+ /**
+ * Import a text message into system's SMS store
+ *
+ * Only default SMS apps can import SMS
+ *
+ * @param address the destination(source) address of the sent(received) message
+ * @param type the type of the message
+ * @param text the message text
+ * @param timestampMillis the message timestamp in milliseconds
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ * @hide
+ */
+ public Uri importTextMessage(String address, int type, String text, long timestampMillis,
+ boolean seen, boolean read) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.importTextMessage(ActivityThread.currentPackageName(),
+ address, type, text, timestampMillis, seen, read);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /** Represents the received SMS message for importing {@hide} */
+ public static final int SMS_TYPE_INCOMING = 0;
+ /** Represents the sent SMS message for importing {@hide} */
+ public static final int SMS_TYPE_OUTGOING = 1;
+
+ /**
+ * Import a multimedia message into system's MMS store. Only the following PDU type is
+ * supported: Retrieve.conf, Send.req, Notification.ind, Delivery.ind, Read-Orig.ind
+ *
+ * Only default SMS apps can import MMS
+ *
+ * @param contentUri the content uri from which to read the PDU of the message to import
+ * @param messageId the optional message id. Use null if not specifying
+ * @param timestampSecs the optional message timestamp. Use -1 if not specifying
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ * @throws IllegalArgumentException if pdu is empty
+ * {@hide}
+ */
+ public Uri importMultimediaMessage(Uri contentUri, String messageId, long timestampSecs,
+ boolean seen, boolean read) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.importMultimediaMessage(ActivityThread.currentPackageName(),
+ contentUri, messageId, timestampSecs, seen, read);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Delete a system stored SMS or MMS message
+ *
+ * Only default SMS apps can delete system stored SMS and MMS messages
+ *
+ * @param messageUri the URI of the stored message
+ * @return true if deletion is successful, false otherwise
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public boolean deleteStoredMessage(Uri messageUri) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.deleteStoredMessage(ActivityThread.currentPackageName(), messageUri);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Delete a system stored SMS or MMS thread
+ *
+ * Only default SMS apps can delete system stored SMS and MMS conversations
+ *
+ * @param conversationId the ID of the message conversation
+ * @return true if deletion is successful, false otherwise
+ * {@hide}
+ */
+ public boolean deleteStoredConversation(long conversationId) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.deleteStoredConversation(
+ ActivityThread.currentPackageName(), conversationId);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Update the status properties of a system stored SMS or MMS message, e.g.
+ * the read status of a message, etc.
+ *
+ * @param messageUri the URI of the stored message
+ * @param statusValues a list of status properties in key-value pairs to update
+ * @return true if update is successful, false otherwise
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public boolean updateStoredMessageStatus(Uri messageUri, ContentValues statusValues) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.updateStoredMessageStatus(ActivityThread.currentPackageName(),
+ messageUri, statusValues);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /** Message status property: whether the message has been seen. 1 means seen, 0 not {@hide} */
+ public static final String MESSAGE_STATUS_SEEN = "seen";
+ /** Message status property: whether the message has been read. 1 means read, 0 not {@hide} */
+ public static final String MESSAGE_STATUS_READ = "read";
+
+ /**
+ * Archive or unarchive a stored conversation
+ *
+ * @param conversationId the ID of the message conversation
+ * @param archived true to archive the conversation, false to unarchive
+ * @return true if update is successful, false otherwise
+ * {@hide}
+ */
+ public boolean archiveStoredConversation(long conversationId, boolean archived) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.archiveStoredConversation(ActivityThread.currentPackageName(),
+ conversationId, archived);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Add a text message draft to system SMS store
+ *
+ * Only default SMS apps can add SMS draft
+ *
+ * @param address the destination address of message
+ * @param text the body of the message to send
+ * @return the URI of the stored draft message
+ * {@hide}
+ */
+ public Uri addTextMessageDraft(String address, String text) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.addTextMessageDraft(ActivityThread.currentPackageName(), address, text);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Add a multimedia message draft to system MMS store
+ *
+ * Only default SMS apps can add MMS draft
+ *
+ * @param contentUri the content uri from which to read the PDU data of the draft MMS
+ * @return the URI of the stored draft message
+ * @throws IllegalArgumentException if pdu is empty
+ * {@hide}
+ */
+ public Uri addMultimediaMessageDraft(Uri contentUri) {
+ if (contentUri == null) {
+ throw new IllegalArgumentException("Uri contentUri null");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.addMultimediaMessageDraft(ActivityThread.currentPackageName(),
+ contentUri);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Send a system stored text message.
+ *
+ * You can only send a failed text message or a draft text message.
+ *
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use the current default SMSC
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredTextMessage(Uri messageUri, String scAddress, PendingIntent sentIntent,
+ PendingIntent deliveryIntent) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendStoredText(
+ getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+ scAddress, sentIntent, deliveryIntent);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a system stored multi-part text message.
+ *
+ * You can only send a failed text message or a draft text message.
+ * The provided <code>PendingIntent</code> lists should match the part number of the
+ * divided text of the stored message by using <code>divideMessage</code>
+ *
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ *
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredMultipartTextMessage(Uri messageUri, String scAddress,
+ ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ ISms iccISms = getISmsServiceOrThrow();
+ iccISms.sendStoredMultipartText(
+ getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+ scAddress, sentIntents, deliveryIntents);
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Send a system stored MMS message
+ *
+ * This is used for sending a previously sent, but failed-to-send, message or
+ * for sending a text message that has been stored as a draft.
+ *
+ * @param messageUri the URI of the stored message
+ * @param configOverrides the carrier-specific messaging configuration values to override for
+ * sending the message.
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed
+ * @throws IllegalArgumentException if messageUri is empty
+ * {@hide}
+ */
+ public void sendStoredMultimediaMessage(Uri messageUri, Bundle configOverrides,
+ PendingIntent sentIntent) {
+ if (messageUri == null) {
+ throw new IllegalArgumentException("Empty message URI");
+ }
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ iMms.sendStoredMessage(
+ getSubscriptionId(), ActivityThread.currentPackageName(), messageUri,
+ configOverrides, sentIntent);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Turns on/off the flag to automatically write sent/received SMS/MMS messages into system
+ *
+ * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+ * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+ * automatically
+ *
+ * This flag can only be changed by default SMS apps
+ *
+ * @param enabled Whether to enable message auto persisting
+ * {@hide}
+ */
+ public void setAutoPersisting(boolean enabled) {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ iMms.setAutoPersisting(ActivityThread.currentPackageName(), enabled);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Get the value of the flag to automatically write sent/received SMS/MMS messages into system
+ *
+ * When this flag is on, all SMS/MMS sent/received are stored by system automatically
+ * When this flag is off, only SMS/MMS sent by non-default SMS apps are stored by system
+ * automatically
+ *
+ * @return the current value of the auto persist flag
+ * {@hide}
+ */
+ public boolean getAutoPersisting() {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.getAutoPersisting();
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return false;
+ }
+
+ /**
+ * Get carrier-dependent configuration values.
+ *
+ * @return bundle key/values pairs of configuration values
+ */
+ public Bundle getCarrierConfigValues() {
+ try {
+ IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
+ if (iMms != null) {
+ return iMms.getCarrierConfigValues(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return null;
+ }
+
+ /**
+ * Create a single use app specific incoming SMS request for the the calling package.
+ *
+ * This method returns a token that if included in a subsequent incoming SMS message will cause
+ * {@code intent} to be sent with the SMS data.
+ *
+ * The token is only good for one use, after an SMS has been received containing the token all
+ * subsequent SMS messages with the token will be routed as normal.
+ *
+ * An app can only have one request at a time, if the app already has a request pending it will
+ * be replaced with a new request.
+ *
+ * @return Token to include in an SMS message. The token will be 11 characters long.
+ * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
+ */
+ public String createAppSpecificSmsToken(PendingIntent intent) {
+ try {
+ ISms iccSms = getISmsServiceOrThrow();
+ return iccSms.createAppSpecificSmsToken(getSubscriptionId(),
+ ActivityThread.currentPackageName(), intent);
+
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ return null;
+ }
+ }
+
+ /**
+ * Filters a bundle to only contain MMS config variables.
+ *
+ * This is for use with bundles returned by {@link CarrierConfigManager} which contain MMS
+ * config and unrelated config. It is assumed that all MMS_CONFIG_* keys are present in the
+ * supplied bundle.
+ *
+ * @param config a Bundle that contains MMS config variables and possibly more.
+ * @return a new Bundle that only contains the MMS_CONFIG_* keys defined above.
+ * @hide
+ */
+ public static Bundle getMmsConfig(BaseBundle config) {
+ Bundle filtered = new Bundle();
+ filtered.putBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID,
+ config.getBoolean(MMS_CONFIG_APPEND_TRANSACTION_ID));
+ filtered.putBoolean(MMS_CONFIG_MMS_ENABLED, config.getBoolean(MMS_CONFIG_MMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_GROUP_MMS_ENABLED,
+ config.getBoolean(MMS_CONFIG_GROUP_MMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED,
+ config.getBoolean(MMS_CONFIG_NOTIFY_WAP_MMSC_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_ALIAS_ENABLED, config.getBoolean(MMS_CONFIG_ALIAS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO,
+ config.getBoolean(MMS_CONFIG_ALLOW_ATTACH_AUDIO));
+ filtered.putBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED,
+ config.getBoolean(MMS_CONFIG_MULTIPART_SMS_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_SMS_DELIVERY_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION,
+ config.getBoolean(MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION));
+ filtered.putBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES,
+ config.getBoolean(MMS_CONFIG_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES));
+ filtered.putBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_MMS_READ_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED,
+ config.getBoolean(MMS_CONFIG_MMS_DELIVERY_REPORT_ENABLED));
+ filtered.putBoolean(MMS_CONFIG_CLOSE_CONNECTION,
+ config.getBoolean(MMS_CONFIG_CLOSE_CONNECTION));
+ filtered.putInt(MMS_CONFIG_MAX_MESSAGE_SIZE, config.getInt(MMS_CONFIG_MAX_MESSAGE_SIZE));
+ filtered.putInt(MMS_CONFIG_MAX_IMAGE_WIDTH, config.getInt(MMS_CONFIG_MAX_IMAGE_WIDTH));
+ filtered.putInt(MMS_CONFIG_MAX_IMAGE_HEIGHT, config.getInt(MMS_CONFIG_MAX_IMAGE_HEIGHT));
+ filtered.putInt(MMS_CONFIG_RECIPIENT_LIMIT, config.getInt(MMS_CONFIG_RECIPIENT_LIMIT));
+ filtered.putInt(MMS_CONFIG_ALIAS_MIN_CHARS, config.getInt(MMS_CONFIG_ALIAS_MIN_CHARS));
+ filtered.putInt(MMS_CONFIG_ALIAS_MAX_CHARS, config.getInt(MMS_CONFIG_ALIAS_MAX_CHARS));
+ filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD,
+ config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_THRESHOLD));
+ filtered.putInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD,
+ config.getInt(MMS_CONFIG_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD));
+ filtered.putInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE,
+ config.getInt(MMS_CONFIG_MESSAGE_TEXT_MAX_SIZE));
+ filtered.putInt(MMS_CONFIG_SUBJECT_MAX_LENGTH,
+ config.getInt(MMS_CONFIG_SUBJECT_MAX_LENGTH));
+ filtered.putInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT,
+ config.getInt(MMS_CONFIG_HTTP_SOCKET_TIMEOUT));
+ filtered.putString(MMS_CONFIG_UA_PROF_TAG_NAME,
+ config.getString(MMS_CONFIG_UA_PROF_TAG_NAME));
+ filtered.putString(MMS_CONFIG_USER_AGENT, config.getString(MMS_CONFIG_USER_AGENT));
+ filtered.putString(MMS_CONFIG_UA_PROF_URL, config.getString(MMS_CONFIG_UA_PROF_URL));
+ filtered.putString(MMS_CONFIG_HTTP_PARAMS, config.getString(MMS_CONFIG_HTTP_PARAMS));
+ filtered.putString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER,
+ config.getString(MMS_CONFIG_EMAIL_GATEWAY_NUMBER));
+ filtered.putString(MMS_CONFIG_NAI_SUFFIX, config.getString(MMS_CONFIG_NAI_SUFFIX));
+ filtered.putBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS,
+ config.getBoolean(MMS_CONFIG_SHOW_CELL_BROADCAST_APP_LINKS));
+ filtered.putBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER,
+ config.getBoolean(MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER));
+ return filtered;
+ }
+
+}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
new file mode 100644
index 0000000..d6e74cf
--- /dev/null
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2008 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.Binder;
+import android.os.Parcel;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.lang.Math;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+
+
+/**
+ * A Short Message Service message.
+ * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
+ */
+public class SmsMessage {
+ private static final String LOG_TAG = "SmsMessage";
+
+ /**
+ * SMS Class enumeration.
+ * See TS 23.038.
+ *
+ */
+ public enum MessageClass{
+ UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
+ }
+
+ /** User data text encoding code unit size */
+ public static final int ENCODING_UNKNOWN = 0;
+ public static final int ENCODING_7BIT = 1;
+ public static final int ENCODING_8BIT = 2;
+ public static final int ENCODING_16BIT = 3;
+ /**
+ * @hide This value is not defined in global standard. Only in Korea, this is used.
+ */
+ public static final int ENCODING_KSC5601 = 4;
+
+ /** The maximum number of payload bytes per message */
+ public static final int MAX_USER_DATA_BYTES = 140;
+
+ /**
+ * The maximum number of payload bytes per message if a user data header
+ * is present. This assumes the header only contains the
+ * CONCATENATED_8_BIT_REFERENCE element.
+ */
+ public static final int MAX_USER_DATA_BYTES_WITH_HEADER = 134;
+
+ /** The maximum number of payload septets per message */
+ public static final int MAX_USER_DATA_SEPTETS = 160;
+
+ /**
+ * The maximum number of payload septets per message if a user data header
+ * is present. This assumes the header only contains the
+ * CONCATENATED_8_BIT_REFERENCE element.
+ */
+ public static final int MAX_USER_DATA_SEPTETS_WITH_HEADER = 153;
+
+ /**
+ * Indicates a 3GPP format SMS message.
+ * @hide pending API council approval
+ */
+ public static final String FORMAT_3GPP = "3gpp";
+
+ /**
+ * Indicates a 3GPP2 format SMS message.
+ * @hide pending API council approval
+ */
+ public static final String FORMAT_3GPP2 = "3gpp2";
+
+ /** Contains actual SmsMessage. Only public for debugging and for framework layer.
+ *
+ * @hide
+ */
+ public SmsMessageBase mWrappedSmsMessage;
+
+ /** Indicates the subId
+ *
+ * @hide
+ */
+ private int mSubId = 0;
+
+ /** set Subscription information
+ *
+ * @hide
+ */
+ public void setSubId(int subId) {
+ mSubId = subId;
+ }
+
+ /** get Subscription information
+ *
+ * @hide
+ */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ public static class SubmitPdu {
+
+ public byte[] encodedScAddress; // Null if not applicable.
+ public byte[] encodedMessage;
+
+ @Override
+ public String toString() {
+ return "SubmitPdu: encodedScAddress = "
+ + Arrays.toString(encodedScAddress)
+ + ", encodedMessage = "
+ + Arrays.toString(encodedMessage);
+ }
+
+ /**
+ * @hide
+ */
+ protected SubmitPdu(SubmitPduBase spb) {
+ this.encodedMessage = spb.encodedMessage;
+ this.encodedScAddress = spb.encodedScAddress;
+ }
+
+ }
+
+ /**
+ * @hide
+ */
+ public SmsMessage(SmsMessageBase smb) {
+ mWrappedSmsMessage = smb;
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU. Guess format based on Voice
+ * technology first, if it fails use other format.
+ * All applications which handle
+ * incoming SMS messages by processing the {@code SMS_RECEIVED_ACTION} broadcast
+ * intent <b>must</b> now pass the new {@code format} String extra from the intent
+ * into the new method {@code createFromPdu(byte[], String)} which takes an
+ * extra format parameter. This is required in order to correctly decode the PDU on
+ * devices that require support for both 3GPP and 3GPP2 formats at the same time,
+ * such as dual-mode GSM/CDMA and CDMA/LTE phones.
+ * @deprecated Use {@link #createFromPdu(byte[], String)} instead.
+ */
+ @Deprecated
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ SmsMessage message = null;
+
+ // cdma(3gpp2) vs gsm(3gpp) format info was not given,
+ // guess from active voice phone type
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ String format = (PHONE_TYPE_CDMA == activePhone) ?
+ SmsConstants.FORMAT_3GPP2 : SmsConstants.FORMAT_3GPP;
+ message = createFromPdu(pdu, format);
+
+ if (null == message || null == message.mWrappedSmsMessage) {
+ // decoding pdu failed based on activePhone type, must be other format
+ format = (PHONE_TYPE_CDMA == activePhone) ?
+ SmsConstants.FORMAT_3GPP : SmsConstants.FORMAT_3GPP2;
+ message = createFromPdu(pdu, format);
+ }
+ return message;
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU with the specified message format. The
+ * message format is passed in the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} as the {@code format}
+ * String extra, and will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
+ * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
+ *
+ * @param pdu the message PDU from the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
+ * @param format the format extra from the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION} intent
+ */
+ public static SmsMessage createFromPdu(byte[] pdu, String format) {
+ SmsMessageBase wrappedMessage;
+
+ if (SmsConstants.FORMAT_3GPP2.equals(format)) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromPdu(pdu);
+ } else if (SmsConstants.FORMAT_3GPP.equals(format)) {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromPdu(pdu);
+ } else {
+ Rlog.e(LOG_TAG, "createFromPdu(): unsupported message format " + format);
+ return null;
+ }
+
+ if (wrappedMessage != null) {
+ return new SmsMessage(wrappedMessage);
+ } else {
+ Rlog.e(LOG_TAG, "createFromPdu(): wrappedMessage is null");
+ return null;
+ }
+ }
+
+ /**
+ * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+ * +CMT unsolicited response (PDU mode, of course)
+ * +CMT: [<alpha>],<length><CR><LF><pdu>
+ *
+ * Only public for debugging and for RIL
+ *
+ * {@hide}
+ */
+ public static SmsMessage newFromCMT(byte[] pdu) {
+ // received SMS in 3GPP format
+ SmsMessageBase wrappedMessage =
+ com.android.internal.telephony.gsm.SmsMessage.newFromCMT(pdu);
+
+ if (wrappedMessage != null) {
+ return new SmsMessage(wrappedMessage);
+ } else {
+ Rlog.e(LOG_TAG, "newFromCMT(): wrappedMessage is null");
+ return null;
+ }
+ }
+
+ /**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by SmsManager.getAllMessagesFromSim + 1.
+ * @param data Record data.
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data) {
+ SmsMessageBase wrappedMessage;
+
+ if (isCdmaVoice()) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+ index, data);
+ } else {
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+ index, data);
+ }
+
+ if (wrappedMessage != null) {
+ return new SmsMessage(wrappedMessage);
+ } else {
+ Rlog.e(LOG_TAG, "createFromEfRecord(): wrappedMessage is null");
+ return null;
+ }
+ }
+
+ /**
+ * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+ * length in bytes (not hex chars) less the SMSC header
+ *
+ * FIXME: This method is only used by a CTS test case that isn't run on CDMA devices.
+ * We should probably deprecate it and remove the obsolete test case.
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ if (isCdmaVoice()) {
+ return com.android.internal.telephony.cdma.SmsMessage.getTPLayerLengthForPDU(pdu);
+ } else {
+ return com.android.internal.telephony.gsm.SmsMessage.getTPLayerLengthForPDU(pdu);
+ }
+ }
+
+ /*
+ * TODO(cleanup): It would make some sense if the result of
+ * preprocessing a message to determine the proper encoding (i.e.
+ * the resulting data structure from calculateLength) could be
+ * passed as an argument to the actual final encoding function.
+ * This would better ensure that the logic behind size calculation
+ * actually matched the encoding.
+ */
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message.
+ *
+ * @param msgBody the message to encode
+ * @param use7bitOnly if true, characters that are not part of the
+ * radio-specific 7-bit encoding are counted as single
+ * space chars. If false, and if the messageBody contains
+ * non-7-bit encodable characters, length is calculated
+ * using a 16-bit encoding.
+ * @return an int[4] with int[0] being the number of SMS's
+ * required, int[1] the number of code units used, and
+ * int[2] is the number of code units remaining until the
+ * next message. int[3] is an indicator of the encoding
+ * code unit size (see the ENCODING_* definitions in SmsConstants)
+ */
+ public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) {
+ // this function is for MO SMS
+ TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
+ com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly,
+ true) :
+ com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly);
+ int ret[] = new int[4];
+ ret[0] = ted.msgCount;
+ ret[1] = ted.codeUnitCount;
+ ret[2] = ted.codeUnitsRemaining;
+ ret[3] = ted.codeUnitSize;
+ return ret;
+ }
+
+ /**
+ * Divide a message text into several fragments, none bigger than
+ * the maximum SMS message text size.
+ *
+ * @param text text, must not be null.
+ * @return an <code>ArrayList</code> of strings that, in order,
+ * comprise the original msg text
+ *
+ * @hide
+ */
+ public static ArrayList<String> fragmentText(String text) {
+ // This function is for MO SMS
+ TextEncodingDetails ted = (useCdmaFormatForMoSms()) ?
+ com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false, true) :
+ com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false);
+
+ // TODO(cleanup): The code here could be rolled into the logic
+ // below cleanly if these MAX_* constants were defined more
+ // flexibly...
+
+ int limit;
+ if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+ int udhLength;
+ if (ted.languageTable != 0 && ted.languageShiftTable != 0) {
+ udhLength = GsmAlphabet.UDH_SEPTET_COST_TWO_SHIFT_TABLES;
+ } else if (ted.languageTable != 0 || ted.languageShiftTable != 0) {
+ udhLength = GsmAlphabet.UDH_SEPTET_COST_ONE_SHIFT_TABLE;
+ } else {
+ udhLength = 0;
+ }
+
+ if (ted.msgCount > 1) {
+ udhLength += GsmAlphabet.UDH_SEPTET_COST_CONCATENATED_MESSAGE;
+ }
+
+ if (udhLength != 0) {
+ udhLength += GsmAlphabet.UDH_SEPTET_COST_LENGTH;
+ }
+
+ limit = SmsConstants.MAX_USER_DATA_SEPTETS - udhLength;
+ } else {
+ if (ted.msgCount > 1) {
+ limit = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+ // If EMS is not supported, break down EMS into single segment SMS
+ // and add page info " x/y".
+ // In the case of UCS2 encoding, we need 8 bytes for this,
+ // but we only have 6 bytes from UDH, so truncate the limit for
+ // each segment by 2 bytes (1 char).
+ // Make sure total number of segments is less than 10.
+ if (!hasEmsSupport() && ted.msgCount < 10) {
+ limit -= 2;
+ }
+ } else {
+ limit = SmsConstants.MAX_USER_DATA_BYTES;
+ }
+ }
+
+ String newMsgBody = null;
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+ newMsgBody = Sms7BitEncodingTranslator.translate(text);
+ }
+ if (TextUtils.isEmpty(newMsgBody)) {
+ newMsgBody = text;
+ }
+ int pos = 0; // Index in code units.
+ int textLen = newMsgBody.length();
+ ArrayList<String> result = new ArrayList<String>(ted.msgCount);
+ while (pos < textLen) {
+ int nextPos = 0; // Counts code units.
+ if (ted.codeUnitSize == SmsConstants.ENCODING_7BIT) {
+ if (useCdmaFormatForMoSms() && ted.msgCount == 1) {
+ // For a singleton CDMA message, the encoding must be ASCII...
+ nextPos = pos + Math.min(limit, textLen - pos);
+ } else {
+ // For multi-segment messages, CDMA 7bit equals GSM 7bit encoding (EMS mode).
+ nextPos = GsmAlphabet.findGsmSeptetLimitIndex(newMsgBody, pos, limit,
+ ted.languageTable, ted.languageShiftTable);
+ }
+ } else { // Assume unicode.
+ nextPos = SmsMessageBase.findNextUnicodePosition(pos, limit, newMsgBody);
+ }
+ if ((nextPos <= pos) || (nextPos > textLen)) {
+ Rlog.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " +
+ nextPos + " >= " + textLen + ")");
+ break;
+ }
+ result.add(newMsgBody.substring(pos, nextPos));
+ pos = nextPos;
+ }
+ return result;
+ }
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message, given the
+ * current encoding.
+ *
+ * @param messageBody the message to encode
+ * @param use7bitOnly if true, characters that are not part of the radio
+ * specific (GSM / CDMA) alphabet encoding are converted to as a
+ * single space characters. If false, a messageBody containing
+ * non-GSM or non-CDMA alphabet characters are encoded using
+ * 16-bit encoding.
+ * @return an int[4] with int[0] being the number of SMS's required, int[1]
+ * the number of code units used, and int[2] is the number of code
+ * units remaining until the next message. int[3] is the encoding
+ * type that should be used for the message.
+ */
+ public static int[] calculateLength(String messageBody, boolean use7bitOnly) {
+ return calculateLength((CharSequence)messageBody, use7bitOnly);
+ }
+
+ /*
+ * TODO(cleanup): It looks like there is now no useful reason why
+ * apps should generate pdus themselves using these routines,
+ * instead of handing the raw data to SMSDispatcher (and thereby
+ * have the phone process do the encoding). Moreover, CDMA now
+ * has shared state (in the form of the msgId system property)
+ * which can only be modified by the phone process, and hence
+ * makes the output of these routines incorrect. Since they now
+ * serve no purpose, they should probably just return null
+ * directly, and be deprecated. Going further in that direction,
+ * the above parsers of serialized pdu data should probably also
+ * be gotten rid of, hiding all but the necessarily visible
+ * structured data from client apps. A possible concern with
+ * doing this is that apps may be using these routines to generate
+ * pdus that are then sent elsewhere, some network server, for
+ * example, and that always returning null would thereby break
+ * otherwise useful apps.
+ */
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message.
+ * This method will not attempt to use any GSM national language 7 bit encodings.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message, boolean statusReportRequested) {
+ SubmitPduBase spb;
+
+ if (useCdmaFormatForMoSms()) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested, null);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, message, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port.
+ * This method will not attempt to use any GSM national language 7 bit encodings.
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, short destinationPort, byte[] data,
+ boolean statusReportRequested) {
+ SubmitPduBase spb;
+
+ if (useCdmaFormatForMoSms()) {
+ spb = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ } else {
+ spb = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(scAddress,
+ destinationAddress, destinationPort, data, statusReportRequested);
+ }
+
+ return new SubmitPdu(spb);
+ }
+
+ /**
+ * Returns the address of the SMS service center that relayed this message
+ * or null if there is none.
+ */
+ public String getServiceCenterAddress() {
+ return mWrappedSmsMessage.getServiceCenterAddress();
+ }
+
+ /**
+ * Returns the originating address (sender) of this SMS message in String
+ * form or null if unavailable
+ */
+ public String getOriginatingAddress() {
+ return mWrappedSmsMessage.getOriginatingAddress();
+ }
+
+ /**
+ * Returns the originating address, or email from address if this message
+ * was from an email gateway. Returns null if originating address
+ * unavailable.
+ */
+ public String getDisplayOriginatingAddress() {
+ return mWrappedSmsMessage.getDisplayOriginatingAddress();
+ }
+
+ /**
+ * Returns the message body as a String, if it exists and is text based.
+ * @return message body is there is one, otherwise null
+ */
+ public String getMessageBody() {
+ return mWrappedSmsMessage.getMessageBody();
+ }
+
+ /**
+ * Returns the class of this message.
+ */
+ public MessageClass getMessageClass() {
+ switch(mWrappedSmsMessage.getMessageClass()) {
+ case CLASS_0: return MessageClass.CLASS_0;
+ case CLASS_1: return MessageClass.CLASS_1;
+ case CLASS_2: return MessageClass.CLASS_2;
+ case CLASS_3: return MessageClass.CLASS_3;
+ default: return MessageClass.UNKNOWN;
+
+ }
+ }
+
+ /**
+ * Returns the message body, or email message body if this message was from
+ * an email gateway. Returns null if message body unavailable.
+ */
+ public String getDisplayMessageBody() {
+ return mWrappedSmsMessage.getDisplayMessageBody();
+ }
+
+ /**
+ * Unofficial convention of a subject line enclosed in parens empty string
+ * if not present
+ */
+ public String getPseudoSubject() {
+ return mWrappedSmsMessage.getPseudoSubject();
+ }
+
+ /**
+ * Returns the service centre timestamp in currentTimeMillis() format
+ */
+ public long getTimestampMillis() {
+ return mWrappedSmsMessage.getTimestampMillis();
+ }
+
+ /**
+ * Returns true if message is an email.
+ *
+ * @return true if this message came through an email gateway and email
+ * sender / subject / parsed body are available
+ */
+ public boolean isEmail() {
+ return mWrappedSmsMessage.isEmail();
+ }
+
+ /**
+ * @return if isEmail() is true, body of the email sent through the gateway.
+ * null otherwise
+ */
+ public String getEmailBody() {
+ return mWrappedSmsMessage.getEmailBody();
+ }
+
+ /**
+ * @return if isEmail() is true, email from address of email sent through
+ * the gateway. null otherwise
+ */
+ public String getEmailFrom() {
+ return mWrappedSmsMessage.getEmailFrom();
+ }
+
+ /**
+ * Get protocol identifier.
+ */
+ public int getProtocolIdentifier() {
+ return mWrappedSmsMessage.getProtocolIdentifier();
+ }
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+ * SMS
+ */
+ public boolean isReplace() {
+ return mWrappedSmsMessage.isReplace();
+ }
+
+ /**
+ * Returns true for CPHS MWI toggle message.
+ *
+ * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
+ * B.4.2
+ */
+ public boolean isCphsMwiMessage() {
+ return mWrappedSmsMessage.isCphsMwiMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ */
+ public boolean isMWIClearMessage() {
+ return mWrappedSmsMessage.isMWIClearMessage();
+ }
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ */
+ public boolean isMWISetMessage() {
+ return mWrappedSmsMessage.isMWISetMessage();
+ }
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ */
+ public boolean isMwiDontStore() {
+ return mWrappedSmsMessage.isMwiDontStore();
+ }
+
+ /**
+ * returns the user data section minus the user data header if one was
+ * present.
+ */
+ public byte[] getUserData() {
+ return mWrappedSmsMessage.getUserData();
+ }
+
+ /**
+ * Returns the raw PDU for the message.
+ *
+ * @return the raw PDU for the message.
+ */
+ public byte[] getPdu() {
+ return mWrappedSmsMessage.getPdu();
+ }
+
+ /**
+ * Returns the status of the message on the SIM (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the SIM. These are:
+ * SmsManager.STATUS_ON_SIM_FREE
+ * SmsManager.STATUS_ON_SIM_READ
+ * SmsManager.STATUS_ON_SIM_UNREAD
+ * SmsManager.STATUS_ON_SIM_SEND
+ * SmsManager.STATUS_ON_SIM_UNSENT
+ * @deprecated Use getStatusOnIcc instead.
+ */
+ @Deprecated public int getStatusOnSim() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ */
+ public int getStatusOnIcc() {
+ return mWrappedSmsMessage.getStatusOnIcc();
+ }
+
+ /**
+ * Returns the record index of the message on the SIM (1-based index).
+ * @return the record index of the message on the SIM, or -1 if this
+ * SmsMessage was not created from a SIM SMS EF record.
+ * @deprecated Use getIndexOnIcc instead.
+ */
+ @Deprecated public int getIndexOnSim() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ */
+ public int getIndexOnIcc() {
+ return mWrappedSmsMessage.getIndexOnIcc();
+ }
+
+ /**
+ * GSM:
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previously
+ * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
+ * description of values.
+ * CDMA:
+ * For not interfering with status codes from GSM, the value is
+ * shifted to the bits 31-16.
+ * The value is composed of an error class (bits 25-24) and a status code (bits 23-16).
+ * Possible codes are described in C.S0015-B, v2.0, 4.5.21.
+ *
+ * @return 0 indicates the previously sent message was received.
+ * See TS 23.040, 9.9.2.3.15 and C.S0015-B, v2.0, 4.5.21
+ * for a description of other possible values.
+ */
+ public int getStatus() {
+ return mWrappedSmsMessage.getStatus();
+ }
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ */
+ public boolean isStatusReportMessage() {
+ return mWrappedSmsMessage.isStatusReportMessage();
+ }
+
+ /**
+ * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+ * this message.
+ */
+ public boolean isReplyPathPresent() {
+ return mWrappedSmsMessage.isReplyPathPresent();
+ }
+
+ /**
+ * Determines whether or not to use CDMA format for MO SMS.
+ * If SMS over IMS is supported, then format is based on IMS SMS format,
+ * otherwise format is based on current phone type.
+ *
+ * @return true if Cdma format should be used for MO SMS, false otherwise.
+ */
+ private static boolean useCdmaFormatForMoSms() {
+ if (!SmsManager.getDefault().isImsSmsSupported()) {
+ // use Voice technology to determine SMS format.
+ return isCdmaVoice();
+ }
+ // IMS is registered with SMS support, check the SMS format supported
+ return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
+ }
+
+ /**
+ * Determines whether or not to current phone type is cdma.
+ *
+ * @return true if current phone type is cdma, false otherwise.
+ */
+ private static boolean isCdmaVoice() {
+ int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
+ return (PHONE_TYPE_CDMA == activePhone);
+ }
+
+ /**
+ * Decide if the carrier supports long SMS.
+ * {@hide}
+ */
+ public static boolean hasEmsSupport() {
+ if (!isNoEmsSupportConfigListExisted()) {
+ return true;
+ }
+
+ String simOperator;
+ String gid;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
+ gid = TelephonyManager.getDefault().getGroupIdLevel1();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ if (!TextUtils.isEmpty(simOperator)) {
+ for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+ if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
+ (TextUtils.isEmpty(currentConfig.mGid1) ||
+ (!TextUtils.isEmpty(currentConfig.mGid1) &&
+ currentConfig.mGid1.equalsIgnoreCase(gid)))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Check where to add " x/y" in each SMS segment, begin or end.
+ * {@hide}
+ */
+ public static boolean shouldAppendPageNumberAsPrefix() {
+ if (!isNoEmsSupportConfigListExisted()) {
+ return false;
+ }
+
+ String simOperator;
+ String gid;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ simOperator = TelephonyManager.getDefault().getSimOperatorNumeric();
+ gid = TelephonyManager.getDefault().getGroupIdLevel1();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ for (NoEmsSupportConfig currentConfig : mNoEmsSupportConfigList) {
+ if (simOperator.startsWith(currentConfig.mOperatorNumber) &&
+ (TextUtils.isEmpty(currentConfig.mGid1) ||
+ (!TextUtils.isEmpty(currentConfig.mGid1)
+ && currentConfig.mGid1.equalsIgnoreCase(gid)))) {
+ return currentConfig.mIsPrefix;
+ }
+ }
+ return false;
+ }
+
+ private static class NoEmsSupportConfig {
+ String mOperatorNumber;
+ String mGid1;
+ boolean mIsPrefix;
+
+ public NoEmsSupportConfig(String[] config) {
+ mOperatorNumber = config[0];
+ mIsPrefix = "prefix".equals(config[1]);
+ mGid1 = config.length > 2 ? config[2] : null;
+ }
+
+ @Override
+ public String toString() {
+ return "NoEmsSupportConfig { mOperatorNumber = " + mOperatorNumber
+ + ", mIsPrefix = " + mIsPrefix + ", mGid1 = " + mGid1 + " }";
+ }
+ }
+
+ private static NoEmsSupportConfig[] mNoEmsSupportConfigList = null;
+ private static boolean mIsNoEmsSupportConfigListLoaded = false;
+
+ private static boolean isNoEmsSupportConfigListExisted() {
+ if (!mIsNoEmsSupportConfigListLoaded) {
+ Resources r = Resources.getSystem();
+ if (r != null) {
+ String[] listArray = r.getStringArray(
+ com.android.internal.R.array.no_ems_support_sim_operators);
+ if ((listArray != null) && (listArray.length > 0)) {
+ mNoEmsSupportConfigList = new NoEmsSupportConfig[listArray.length];
+ for (int i=0; i<listArray.length; i++) {
+ mNoEmsSupportConfigList[i] = new NoEmsSupportConfig(listArray[i].split(";"));
+ }
+ }
+ mIsNoEmsSupportConfigListLoaded = true;
+ }
+ }
+
+ if (mNoEmsSupportConfigList != null && mNoEmsSupportConfigList.length != 0) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/telephony/java/android/telephony/Telephony.java b/telephony/java/android/telephony/Telephony.java
new file mode 100644
index 0000000..943a6ca
--- /dev/null
+++ b/telephony/java/android/telephony/Telephony.java
@@ -0,0 +1,2975 @@
+/*
+ * Copyright (C) 2006 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.provider;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.sqlite.SqliteWrapper;
+import android.net.Uri;
+import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
+import android.text.TextUtils;
+import android.telephony.Rlog;
+import android.util.Patterns;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SmsApplication;
+
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The Telephony provider contains data related to phone operation, specifically SMS and MMS
+ * messages and access to the APN list, including the MMSC to use.
+ *
+ * <p class="note"><strong>Note:</strong> These APIs are not available on all Android-powered
+ * devices. If your app depends on telephony features such as for managing SMS messages, include
+ * a <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}
+ * </a> element in your manifest that declares the {@code "android.hardware.telephony"} hardware
+ * feature. Alternatively, you can check for telephony availability at runtime using either
+ * {@link android.content.pm.PackageManager#hasSystemFeature
+ * hasSystemFeature(PackageManager.FEATURE_TELEPHONY)} or {@link
+ * android.telephony.TelephonyManager#getPhoneType}.</p>
+ *
+ * <h3>Creating an SMS app</h3>
+ *
+ * <p>Only the default SMS app (selected by the user in system settings) is able to write to the
+ * SMS Provider (the tables defined within the {@code Telephony} class) and only the default SMS
+ * app receives the {@link android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION} broadcast
+ * when the user receives an SMS or the {@link
+ * android.provider.Telephony.Sms.Intents#WAP_PUSH_DELIVER_ACTION} broadcast when the user
+ * receives an MMS.</p>
+ *
+ * <p>Any app that wants to behave as the user's default SMS app must handle the following intents:
+ * <ul>
+ * <li>In a broadcast receiver, include an intent filter for {@link Sms.Intents#SMS_DELIVER_ACTION}
+ * (<code>"android.provider.Telephony.SMS_DELIVER"</code>). The broadcast receiver must also
+ * require the {@link android.Manifest.permission#BROADCAST_SMS} permission.
+ * <p>This allows your app to directly receive incoming SMS messages.</p></li>
+ * <li>In a broadcast receiver, include an intent filter for {@link
+ * Sms.Intents#WAP_PUSH_DELIVER_ACTION}} ({@code "android.provider.Telephony.WAP_PUSH_DELIVER"})
+ * with the MIME type <code>"application/vnd.wap.mms-message"</code>.
+ * The broadcast receiver must also require the {@link
+ * android.Manifest.permission#BROADCAST_WAP_PUSH} permission.
+ * <p>This allows your app to directly receive incoming MMS messages.</p></li>
+ * <li>In your activity that delivers new messages, include an intent filter for
+ * {@link android.content.Intent#ACTION_SENDTO} (<code>"android.intent.action.SENDTO"
+ * </code>) with schemas, <code>sms:</code>, <code>smsto:</code>, <code>mms:</code>, and
+ * <code>mmsto:</code>.
+ * <p>This allows your app to receive intents from other apps that want to deliver a
+ * message.</p></li>
+ * <li>In a service, include an intent filter for {@link
+ * android.telephony.TelephonyManager#ACTION_RESPOND_VIA_MESSAGE}
+ * (<code>"android.intent.action.RESPOND_VIA_MESSAGE"</code>) with schemas,
+ * <code>sms:</code>, <code>smsto:</code>, <code>mms:</code>, and <code>mmsto:</code>.
+ * This service must also require the {@link
+ * android.Manifest.permission#SEND_RESPOND_VIA_MESSAGE} permission.
+ * <p>This allows users to respond to incoming phone calls with an immediate text message
+ * using your app.</p></li>
+ * </ul>
+ *
+ * <p>Other apps that are not selected as the default SMS app can only <em>read</em> the SMS
+ * Provider, but may also be notified when a new SMS arrives by listening for the {@link
+ * Sms.Intents#SMS_RECEIVED_ACTION}
+ * broadcast, which is a non-abortable broadcast that may be delivered to multiple apps. This
+ * broadcast is intended for apps that—while not selected as the default SMS app—need to
+ * read special incoming messages such as to perform phone number verification.</p>
+ *
+ * <p>For more information about building SMS apps, read the blog post, <a
+ * href="http://android-developers.blogspot.com/2013/10/getting-your-sms-apps-ready-for-kitkat.html"
+ * >Getting Your SMS Apps Ready for KitKat</a>.</p>
+ *
+ */
+public final class Telephony {
+ private static final String TAG = "Telephony";
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Telephony() {
+ }
+
+ /**
+ * Base columns for tables that contain text-based SMSs.
+ */
+ public interface TextBasedSmsColumns {
+
+ /** Message type: all messages. */
+ public static final int MESSAGE_TYPE_ALL = 0;
+
+ /** Message type: inbox. */
+ public static final int MESSAGE_TYPE_INBOX = 1;
+
+ /** Message type: sent messages. */
+ public static final int MESSAGE_TYPE_SENT = 2;
+
+ /** Message type: drafts. */
+ public static final int MESSAGE_TYPE_DRAFT = 3;
+
+ /** Message type: outbox. */
+ public static final int MESSAGE_TYPE_OUTBOX = 4;
+
+ /** Message type: failed outgoing message. */
+ public static final int MESSAGE_TYPE_FAILED = 5;
+
+ /** Message type: queued to send later. */
+ public static final int MESSAGE_TYPE_QUEUED = 6;
+
+ /**
+ * The type of message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The thread ID of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String THREAD_ID = "thread_id";
+
+ /**
+ * The address of the other party.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADDRESS = "address";
+
+ /**
+ * The date the message was received.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * The date the message was sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
+ * Has the message been read?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * Has the message been seen by the user? The "seen" flag determines
+ * whether we need to show a notification.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String SEEN = "seen";
+
+ /**
+ * {@code TP-Status} value for the message, or -1 if no status has been received.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String STATUS = "status";
+
+ /** TP-Status: no status received. */
+ public static final int STATUS_NONE = -1;
+ /** TP-Status: complete. */
+ public static final int STATUS_COMPLETE = 0;
+ /** TP-Status: pending. */
+ public static final int STATUS_PENDING = 32;
+ /** TP-Status: failed. */
+ public static final int STATUS_FAILED = 64;
+
+ /**
+ * The subject of the message, if present.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SUBJECT = "subject";
+
+ /**
+ * The body of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String BODY = "body";
+
+ /**
+ * The ID of the sender of the conversation, if present.
+ * <P>Type: INTEGER (reference to item in {@code content://contacts/people})</P>
+ */
+ public static final String PERSON = "person";
+
+ /**
+ * The protocol identifier code.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PROTOCOL = "protocol";
+
+ /**
+ * Is the {@code TP-Reply-Path} flag set?
+ * <P>Type: BOOLEAN</P>
+ */
+ public static final String REPLY_PATH_PRESENT = "reply_path_present";
+
+ /**
+ * The service center (SC) through which to send the message, if present.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SERVICE_CENTER = "service_center";
+
+ /**
+ * Is the message locked?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String LOCKED = "locked";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be
+ * < 0 if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
+ * The MTU size of the mobile interface to which the APN connected
+ * @hide
+ */
+ public static final String MTU = "mtu";
+
+ /**
+ * Error code associated with sending or receiving this message
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "error_code";
+
+ /**
+ * The identity of the sender of a sent message. It is
+ * usually the package name of the app which sends the message.
+ * <p class="note"><strong>Note:</strong>
+ * This column is read-only. It is set by the provider and can not be changed by apps.
+ * <p>Type: TEXT</p>
+ */
+ public static final String CREATOR = "creator";
+ }
+
+ /**
+ * Contains all text-based SMS messages.
+ */
+ public static final class Sms implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Sms() {
+ }
+
+ /**
+ * Used to determine the currently configured default SMS package.
+ * @param context context of the requesting application
+ * @return package name for the default SMS package or null
+ */
+ public static String getDefaultSmsPackage(Context context) {
+ ComponentName component = SmsApplication.getDefaultSmsApplication(context, false);
+ if (component != null) {
+ return component.getPackageName();
+ }
+ return null;
+ }
+
+ /**
+ * Return cursor for table query.
+ * @hide
+ */
+ public static Cursor query(ContentResolver cr, String[] projection) {
+ return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+ }
+
+ /**
+ * Return cursor for table query.
+ * @hide
+ */
+ public static Cursor query(ContentResolver cr, String[] projection,
+ String where, String orderBy) {
+ return cr.query(CONTENT_URI, projection, where,
+ null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the given URI.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessageToUri(ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, uri, address, body, subject, date, read, deliveryReport, -1L);
+ }
+
+ /**
+ * Add an SMS to the given URI.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessageToUri(int subId, ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport) {
+ return addMessageToUri(subId, resolver, uri, address, body, subject,
+ date, read, deliveryReport, -1L);
+ }
+
+ /**
+ * Add an SMS to the given URI with the specified thread ID.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @param threadId the thread_id of the message
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessageToUri(ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport, long threadId) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, uri, address, body, subject,
+ date, read, deliveryReport, threadId);
+ }
+
+ /**
+ * Add an SMS to the given URI with thread_id specified.
+ *
+ * @param resolver the content resolver to use
+ * @param uri the URI to add the message to
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param deliveryReport true if a delivery report was requested, false if not
+ * @param threadId the thread_id of the message
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessageToUri(int subId, ContentResolver resolver,
+ Uri uri, String address, String body, String subject,
+ Long date, boolean read, boolean deliveryReport, long threadId) {
+ ContentValues values = new ContentValues(8);
+ Rlog.v(TAG,"Telephony addMessageToUri sub id: " + subId);
+
+ values.put(SUBSCRIPTION_ID, subId);
+ values.put(ADDRESS, address);
+ if (date != null) {
+ values.put(DATE, date);
+ }
+ values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
+ values.put(SUBJECT, subject);
+ values.put(BODY, body);
+ if (deliveryReport) {
+ values.put(STATUS, STATUS_PENDING);
+ }
+ if (threadId != -1L) {
+ values.put(THREAD_ID, threadId);
+ }
+ return resolver.insert(uri, values);
+ }
+
+ /**
+ * Move a message to the given folder.
+ *
+ * @param context the context to use
+ * @param uri the message to move
+ * @param folder the folder to move to
+ * @return true if the operation succeeded
+ * @hide
+ */
+ public static boolean moveMessageToFolder(Context context,
+ Uri uri, int folder, int error) {
+ if (uri == null) {
+ return false;
+ }
+
+ boolean markAsUnread = false;
+ boolean markAsRead = false;
+ switch(folder) {
+ case MESSAGE_TYPE_INBOX:
+ case MESSAGE_TYPE_DRAFT:
+ break;
+ case MESSAGE_TYPE_OUTBOX:
+ case MESSAGE_TYPE_SENT:
+ markAsRead = true;
+ break;
+ case MESSAGE_TYPE_FAILED:
+ case MESSAGE_TYPE_QUEUED:
+ markAsUnread = true;
+ break;
+ default:
+ return false;
+ }
+
+ ContentValues values = new ContentValues(3);
+
+ values.put(TYPE, folder);
+ if (markAsUnread) {
+ values.put(READ, 0);
+ } else if (markAsRead) {
+ values.put(READ, 1);
+ }
+ values.put(ERROR_CODE, error);
+
+ return 1 == SqliteWrapper.update(context, context.getContentResolver(),
+ uri, values, null, null);
+ }
+
+ /**
+ * Returns true iff the folder (message type) identifies an
+ * outgoing message.
+ * @hide
+ */
+ public static boolean isOutgoingFolder(int messageType) {
+ return (messageType == MESSAGE_TYPE_FAILED)
+ || (messageType == MESSAGE_TYPE_OUTBOX)
+ || (messageType == MESSAGE_TYPE_SENT)
+ || (messageType == MESSAGE_TYPE_QUEUED);
+ }
+
+ /**
+ * Contains all text-based SMS messages in the SMS app inbox.
+ */
+ public static final class Inbox implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Inbox() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date,
+ boolean read) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, CONTENT_URI, address, body, subject, date, read, false);
+ }
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param read true if the message has been read, false if not
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(int subId, ContentResolver resolver,
+ String address, String body, String subject, Long date, boolean read) {
+ return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+ subject, date, read, false);
+ }
+ }
+
+ /**
+ * Contains all sent text-based SMS messages in the SMS app.
+ */
+ public static final class Sent implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Sent() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/sent");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, CONTENT_URI, address, body, subject, date, true, false);
+ }
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(int subId, ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+ subject, date, true, false);
+ }
+ }
+
+ /**
+ * Contains all sent text-based SMS messages in the SMS app.
+ */
+ public static final class Draft implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Draft() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/draft");
+
+ /**
+ * @hide
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, CONTENT_URI, address, body, subject, date, true, false);
+ }
+
+ /**
+ * Add an SMS to the Draft box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(int subId, ContentResolver resolver,
+ String address, String body, String subject, Long date) {
+ return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+ subject, date, true, false);
+ }
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all pending outgoing text-based SMS messages.
+ */
+ public static final class Outbox implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Outbox() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/outbox");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Add an SMS to the outbox.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the pseudo-subject of the message
+ * @param date the timestamp for the message
+ * @param deliveryReport whether a delivery report was requested for the message
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(ContentResolver resolver,
+ String address, String body, String subject, Long date,
+ boolean deliveryReport, long threadId) {
+ return addMessageToUri(SubscriptionManager.getDefaultSmsSubscriptionId(),
+ resolver, CONTENT_URI, address, body, subject, date,
+ true, deliveryReport, threadId);
+ }
+
+ /**
+ * Add an SMS to the Out box.
+ *
+ * @param resolver the content resolver to use
+ * @param address the address of the sender
+ * @param body the body of the message
+ * @param subject the psuedo-subject of the message
+ * @param date the timestamp for the message
+ * @param deliveryReport whether a delivery report was requested for the message
+ * @param subId the subscription which the message belongs to
+ * @return the URI for the new message
+ * @hide
+ */
+ public static Uri addMessage(int subId, ContentResolver resolver,
+ String address, String body, String subject, Long date,
+ boolean deliveryReport, long threadId) {
+ return addMessageToUri(subId, resolver, CONTENT_URI, address, body,
+ subject, date, true, deliveryReport, threadId);
+ }
+ }
+
+ /**
+ * Contains all sent text-based SMS messages in the SMS app.
+ */
+ public static final class Conversations
+ implements BaseColumns, TextBasedSmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Conversations() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://sms/conversations");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * The first 45 characters of the body of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SNIPPET = "snippet";
+
+ /**
+ * The number of messages in the conversation.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_COUNT = "msg_count";
+ }
+
+ /**
+ * Contains constants for SMS related Intents that are broadcast.
+ */
+ public static final class Intents {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Intents() {
+ }
+
+ /**
+ * Set by BroadcastReceiver to indicate that the message was handled
+ * successfully.
+ */
+ public static final int RESULT_SMS_HANDLED = 1;
+
+ /**
+ * Set by BroadcastReceiver to indicate a generic error while
+ * processing the message.
+ */
+ public static final int RESULT_SMS_GENERIC_ERROR = 2;
+
+ /**
+ * Set by BroadcastReceiver to indicate insufficient memory to store
+ * the message.
+ */
+ public static final int RESULT_SMS_OUT_OF_MEMORY = 3;
+
+ /**
+ * Set by BroadcastReceiver to indicate that the message, while
+ * possibly valid, is of a format or encoding that is not
+ * supported.
+ */
+ public static final int RESULT_SMS_UNSUPPORTED = 4;
+
+ /**
+ * Set by BroadcastReceiver to indicate a duplicate incoming message.
+ */
+ public static final int RESULT_SMS_DUPLICATED = 5;
+
+ /**
+ * Activity action: Ask the user to change the default
+ * SMS application. This will show a dialog that asks the
+ * user whether they want to replace the current default
+ * SMS application with the one specified in
+ * {@link #EXTRA_PACKAGE_NAME}.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANGE_DEFAULT =
+ "android.provider.Telephony.ACTION_CHANGE_DEFAULT";
+
+ /**
+ * The PackageName string passed in as an
+ * extra for {@link #ACTION_CHANGE_DEFAULT}
+ *
+ * @see #ACTION_CHANGE_DEFAULT
+ */
+ public static final String EXTRA_PACKAGE_NAME = "package";
+
+ /**
+ * Broadcast Action: A new text-based SMS message has been received
+ * by the device. This intent will only be delivered to the default
+ * sms app. That app is responsible for writing the message and notifying
+ * the user. The intent will have the following extra values:</p>
+ *
+ * <ul>
+ * <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+ * that make up the message.</li>
+ * <li><em>"format"</em> - A String describing the format of the PDUs. It can
+ * be either "3gpp" or "3gpp2".</li>
+ * <li><em>"subscription"</em> - An optional long value of the subscription id which
+ * received the message.</li>
+ * <li><em>"slot"</em> - An optional int value of the SIM slot containing the
+ * subscription.</li>
+ * <li><em>"phone"</em> - An optional int value of the phone id associated with the
+ * subscription.</li>
+ * <li><em>"errorCode"</em> - An optional int error code associated with receiving
+ * the message.</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>
+ *
+ * <p class="note"><strong>Note:</strong>
+ * The broadcast receiver that filters for this intent must declare
+ * {@link android.Manifest.permission#BROADCAST_SMS} as a required permission in
+ * the <a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code
+ * <receiver>}</a> tag.
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_DELIVER_ACTION =
+ "android.provider.Telephony.SMS_DELIVER";
+
+ /**
+ * Broadcast Action: A new text-based SMS message has been received
+ * by the device. This intent will be delivered to all registered
+ * receivers as a notification. These apps are not expected to write the
+ * message or notify the user. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+ * that make up the message.</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>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_RECEIVED";
+
+ /**
+ * Broadcast Action: A new data based SMS message has been received
+ * by the device. This intent will be delivered to all registered
+ * receivers as a notification. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>"pdus"</em> - An Object[] of byte[]s containing the PDUs
+ * that make up the message.</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>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String DATA_SMS_RECEIVED_ACTION =
+ "android.intent.action.DATA_SMS_RECEIVED";
+
+ /**
+ * Broadcast Action: A new WAP PUSH message has been received by the
+ * device. This intent will only be delivered to the default
+ * sms app. That app is responsible for writing the message and notifying
+ * the user. The intent will have the following extra values:</p>
+ *
+ * <ul>
+ * <li><em>"transactionId"</em> - (Integer) The WAP transaction ID</li>
+ * <li><em>"pduType"</em> - (Integer) The WAP PDU type</li>
+ * <li><em>"header"</em> - (byte[]) The header of the message</li>
+ * <li><em>"data"</em> - (byte[]) The data payload of the message</li>
+ * <li><em>"contentTypeParameters" </em>
+ * -(HashMap<String,String>) Any parameters associated with the content type
+ * (decoded from the WSP Content-Type header)</li>
+ * <li><em>"subscription"</em> - An optional long value of the subscription id which
+ * received the message.</li>
+ * <li><em>"slot"</em> - An optional int value of the SIM slot containing the
+ * subscription.</li>
+ * <li><em>"phone"</em> - An optional int value of the phone id associated with the
+ * subscription.</li>
+ * </ul>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>The contentTypeParameters extra value is map of content parameters keyed by
+ * their names.</p>
+ *
+ * <p>If any unassigned well-known parameters are encountered, the key of the map will
+ * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter. If
+ * a parameter has No-Value the value in the map will be null.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_MMS} or
+ * {@link android.Manifest.permission#RECEIVE_WAP_PUSH} (depending on WAP PUSH type) to
+ * receive.</p>
+ *
+ * <p class="note"><strong>Note:</strong>
+ * The broadcast receiver that filters for this intent must declare
+ * {@link android.Manifest.permission#BROADCAST_WAP_PUSH} as a required permission in
+ * the <a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code
+ * <receiver>}</a> tag.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WAP_PUSH_DELIVER_ACTION =
+ "android.provider.Telephony.WAP_PUSH_DELIVER";
+
+ /**
+ * Broadcast Action: A new WAP PUSH message has been received by the
+ * device. This intent will be delivered to all registered
+ * receivers as a notification. These apps are not expected to write the
+ * message or notify the user. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <li><em>"transactionId"</em> - (Integer) The WAP transaction ID</li>
+ * <li><em>"pduType"</em> - (Integer) The WAP PDU type</li>
+ * <li><em>"header"</em> - (byte[]) The header of the message</li>
+ * <li><em>"data"</em> - (byte[]) The data payload of the message</li>
+ * <li><em>"contentTypeParameters"</em>
+ * - (HashMap<String,String>) Any parameters associated with the content type
+ * (decoded from the WSP Content-Type header)</li>
+ * </ul>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>The contentTypeParameters extra value is map of content parameters keyed by
+ * their names.</p>
+ *
+ * <p>If any unassigned well-known parameters are encountered, the key of the map will
+ * be 'unassigned/0x...', where '...' is the hex value of the unassigned parameter. If
+ * a parameter has No-Value the value in the map will be null.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_MMS} or
+ * {@link android.Manifest.permission#RECEIVE_WAP_PUSH} (depending on WAP PUSH type) to
+ * receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String WAP_PUSH_RECEIVED_ACTION =
+ "android.provider.Telephony.WAP_PUSH_RECEIVED";
+
+ /**
+ * Broadcast Action: A new Cell Broadcast message has been received
+ * by the device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <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
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_CB_RECEIVED_ACTION =
+ "android.provider.Telephony.SMS_CB_RECEIVED";
+
+ /**
+ * Action: A SMS based carrier provision intent. Used to identify default
+ * carrier provisioning app on the device.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @TestApi
+ public static final String SMS_CARRIER_PROVISION_ACTION =
+ "android.provider.Telephony.SMS_CARRIER_PROVISION";
+
+ /**
+ * Broadcast Action: A new Emergency Broadcast message has been received
+ * by the device. The intent will have the following extra
+ * values:</p>
+ *
+ * <ul>
+ * <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
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_EMERGENCY_BROADCAST} to
+ * receive.</p>
+ * @removed
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION =
+ "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>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</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.
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SIM_FULL_ACTION =
+ "android.provider.Telephony.SIM_FULL";
+
+ /**
+ * Broadcast Action: An incoming SMS has been rejected by the
+ * telephony framework. This intent is sent in lieu of any
+ * of the RECEIVED_ACTION intents. The intent will have the
+ * following extra value:</p>
+ *
+ * <ul>
+ * <li><em>"result"</em> - An int result code, e.g. {@link #RESULT_SMS_OUT_OF_MEMORY}
+ * indicating the error returned to the network.</li>
+ * </ul>
+ *
+ * <p>Requires {@link android.Manifest.permission#RECEIVE_SMS} to receive.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String SMS_REJECTED_ACTION =
+ "android.provider.Telephony.SMS_REJECTED";
+
+ /**
+ * Broadcast Action: An incoming MMS has been downloaded. The intent is sent to all
+ * users, except for secondary users where SMS has been disabled and to managed
+ * profiles.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String MMS_DOWNLOADED_ACTION =
+ "android.provider.Telephony.MMS_DOWNLOADED";
+
+ /**
+ * Broadcast action: When the default SMS package changes,
+ * the previous default SMS package and the new default SMS
+ * package are sent this broadcast to notify them of the change.
+ * A boolean is specified in {@link #EXTRA_IS_DEFAULT_SMS_APP} to
+ * indicate whether the package is the new default SMS package.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEFAULT_SMS_PACKAGE_CHANGED =
+ "android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED";
+
+ /**
+ * The IsDefaultSmsApp boolean passed as an
+ * extra for {@link #ACTION_DEFAULT_SMS_PACKAGE_CHANGED} to indicate whether the
+ * SMS app is becoming the default SMS app or is no longer the default.
+ *
+ * @see #ACTION_DEFAULT_SMS_PACKAGE_CHANGED
+ */
+ public static final String EXTRA_IS_DEFAULT_SMS_APP =
+ "android.provider.extra.IS_DEFAULT_SMS_APP";
+
+ /**
+ * Broadcast action: When a change is made to the SmsProvider or
+ * MmsProvider by a process other than the default SMS application,
+ * this intent is broadcast to the default SMS application so it can
+ * re-sync or update the change. The uri that was used to call the provider
+ * can be retrieved from the intent with getData(). The actual affected uris
+ * (which would depend on the selection specified) are not included.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_EXTERNAL_PROVIDER_CHANGE =
+ "android.provider.action.EXTERNAL_PROVIDER_CHANGE";
+
+ /**
+ * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a
+ * {@link #DATA_SMS_RECEIVED_ACTION} intent.
+ *
+ * @param intent the intent to read from
+ * @return an array of SmsMessages for the PDUs
+ */
+ public static SmsMessage[] getMessagesFromIntent(Intent intent) {
+ Object[] messages;
+ try {
+ messages = (Object[]) intent.getSerializableExtra("pdus");
+ }
+ catch (ClassCastException e) {
+ Rlog.e(TAG, "getMessagesFromIntent: " + e);
+ return null;
+ }
+
+ if (messages == null) {
+ Rlog.e(TAG, "pdus does not exist in the intent");
+ return null;
+ }
+
+ String format = intent.getStringExtra("format");
+ int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ SubscriptionManager.getDefaultSmsSubscriptionId());
+
+ Rlog.v(TAG, " getMessagesFromIntent sub_id : " + subId);
+
+ int pduCount = messages.length;
+ SmsMessage[] msgs = new SmsMessage[pduCount];
+
+ for (int i = 0; i < pduCount; i++) {
+ byte[] pdu = (byte[]) messages[i];
+ msgs[i] = SmsMessage.createFromPdu(pdu, format);
+ if (msgs[i] != null) msgs[i].setSubId(subId);
+ }
+ return msgs;
+ }
+ }
+ }
+
+ /**
+ * Base columns for tables that contain MMSs.
+ */
+ public interface BaseMmsColumns extends BaseColumns {
+
+ /** Message box: all messages. */
+ public static final int MESSAGE_BOX_ALL = 0;
+ /** Message box: inbox. */
+ public static final int MESSAGE_BOX_INBOX = 1;
+ /** Message box: sent messages. */
+ public static final int MESSAGE_BOX_SENT = 2;
+ /** Message box: drafts. */
+ public static final int MESSAGE_BOX_DRAFTS = 3;
+ /** Message box: outbox. */
+ public static final int MESSAGE_BOX_OUTBOX = 4;
+ /** Message box: failed. */
+ public static final int MESSAGE_BOX_FAILED = 5;
+
+ /**
+ * The thread ID of the message.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String THREAD_ID = "thread_id";
+
+ /**
+ * The date the message was received.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * The date the message was sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE_SENT = "date_sent";
+
+ /**
+ * The box which the message belongs to, e.g. {@link #MESSAGE_BOX_INBOX}.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_BOX = "msg_box";
+
+ /**
+ * Has the message been read?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * Has the message been seen by the user? The "seen" flag determines
+ * whether we need to show a new message notification.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String SEEN = "seen";
+
+ /**
+ * Does the message have only a text part (can also have a subject) with
+ * no picture, slideshow, sound, etc. parts?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String TEXT_ONLY = "text_only";
+
+ /**
+ * The {@code Message-ID} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_ID = "m_id";
+
+ /**
+ * The subject of the message, if present.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SUBJECT = "sub";
+
+ /**
+ * The character set of the subject, if present.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SUBJECT_CHARSET = "sub_cs";
+
+ /**
+ * The {@code Content-Type} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_TYPE = "ct_t";
+
+ /**
+ * The {@code Content-Location} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_LOCATION = "ct_l";
+
+ /**
+ * The expiry time of the message.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String EXPIRY = "exp";
+
+ /**
+ * The class of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_CLASS = "m_cls";
+
+ /**
+ * The type of the message defined by MMS spec.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_TYPE = "m_type";
+
+ /**
+ * The version of the specification that this message conforms to.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MMS_VERSION = "v";
+
+ /**
+ * The size of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_SIZE = "m_size";
+
+ /**
+ * The priority of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PRIORITY = "pri";
+
+ /**
+ * The {@code read-report} of the message.
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String READ_REPORT = "rr";
+
+ /**
+ * Is read report allowed?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String REPORT_ALLOWED = "rpt_a";
+
+ /**
+ * The {@code response-status} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RESPONSE_STATUS = "resp_st";
+
+ /**
+ * The {@code status} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String STATUS = "st";
+
+ /**
+ * The {@code transaction-id} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TRANSACTION_ID = "tr_id";
+
+ /**
+ * The {@code retrieve-status} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RETRIEVE_STATUS = "retr_st";
+
+ /**
+ * The {@code retrieve-text} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RETRIEVE_TEXT = "retr_txt";
+
+ /**
+ * The character set of the retrieve-text.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RETRIEVE_TEXT_CHARSET = "retr_txt_cs";
+
+ /**
+ * The {@code read-status} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String READ_STATUS = "read_status";
+
+ /**
+ * The {@code content-class} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_CLASS = "ct_cls";
+
+ /**
+ * The {@code delivery-report} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DELIVERY_REPORT = "d_rpt";
+
+ /**
+ * The {@code delivery-time-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String DELIVERY_TIME_TOKEN = "d_tm_tok";
+
+ /**
+ * The {@code delivery-time} of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String DELIVERY_TIME = "d_tm";
+
+ /**
+ * The {@code response-text} of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RESPONSE_TEXT = "resp_txt";
+
+ /**
+ * The {@code sender-visibility} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String SENDER_VISIBILITY = "s_vis";
+
+ /**
+ * The {@code reply-charging} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING = "r_chg";
+
+ /**
+ * The {@code reply-charging-deadline-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING_DEADLINE_TOKEN = "r_chg_dl_tok";
+
+ /**
+ * The {@code reply-charging-deadline} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING_DEADLINE = "r_chg_dl";
+
+ /**
+ * The {@code reply-charging-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING_ID = "r_chg_id";
+
+ /**
+ * The {@code reply-charging-size} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_CHARGING_SIZE = "r_chg_sz";
+
+ /**
+ * The {@code previously-sent-by} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String PREVIOUSLY_SENT_BY = "p_s_by";
+
+ /**
+ * The {@code previously-sent-date} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String PREVIOUSLY_SENT_DATE = "p_s_d";
+
+ /**
+ * The {@code store} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STORE = "store";
+
+ /**
+ * The {@code mm-state} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MM_STATE = "mm_st";
+
+ /**
+ * The {@code mm-flags-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MM_FLAGS_TOKEN = "mm_flg_tok";
+
+ /**
+ * The {@code mm-flags} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MM_FLAGS = "mm_flg";
+
+ /**
+ * The {@code store-status} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STORE_STATUS = "store_st";
+
+ /**
+ * The {@code store-status-text} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STORE_STATUS_TEXT = "store_st_txt";
+
+ /**
+ * The {@code stored} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STORED = "stored";
+
+ /**
+ * The {@code totals} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String TOTALS = "totals";
+
+ /**
+ * The {@code mbox-totals} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MBOX_TOTALS = "mb_t";
+
+ /**
+ * The {@code mbox-totals-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MBOX_TOTALS_TOKEN = "mb_t_tok";
+
+ /**
+ * The {@code quotas} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String QUOTAS = "qt";
+
+ /**
+ * The {@code mbox-quotas} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MBOX_QUOTAS = "mb_qt";
+
+ /**
+ * The {@code mbox-quotas-token} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MBOX_QUOTAS_TOKEN = "mb_qt_tok";
+
+ /**
+ * The {@code message-count} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String MESSAGE_COUNT = "m_cnt";
+
+ /**
+ * The {@code start} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String START = "start";
+
+ /**
+ * The {@code distribution-indicator} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String DISTRIBUTION_INDICATOR = "d_ind";
+
+ /**
+ * The {@code element-descriptor} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String ELEMENT_DESCRIPTOR = "e_des";
+
+ /**
+ * The {@code limit} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String LIMIT = "limit";
+
+ /**
+ * The {@code recommended-retrieval-mode} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String RECOMMENDED_RETRIEVAL_MODE = "r_r_mod";
+
+ /**
+ * The {@code recommended-retrieval-mode-text} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String RECOMMENDED_RETRIEVAL_MODE_TEXT = "r_r_mod_txt";
+
+ /**
+ * The {@code status-text} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String STATUS_TEXT = "st_txt";
+
+ /**
+ * The {@code applic-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String APPLIC_ID = "apl_id";
+
+ /**
+ * The {@code reply-applic-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLY_APPLIC_ID = "r_apl_id";
+
+ /**
+ * The {@code aux-applic-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String AUX_APPLIC_ID = "aux_apl_id";
+
+ /**
+ * The {@code drm-content} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String DRM_CONTENT = "drm_c";
+
+ /**
+ * The {@code adaptation-allowed} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String ADAPTATION_ALLOWED = "adp_a";
+
+ /**
+ * The {@code replace-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String REPLACE_ID = "repl_id";
+
+ /**
+ * The {@code cancel-id} of the message.
+ * <P>Type: TEXT</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String CANCEL_ID = "cl_id";
+
+ /**
+ * The {@code cancel-status} of the message.
+ * <P>Type: INTEGER</P>
+ * @deprecated this column is no longer supported.
+ * @hide
+ */
+ @Deprecated
+ public static final String CANCEL_STATUS = "cl_st";
+
+ /**
+ * Is the message locked?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String LOCKED = "locked";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be
+ * < 0 if the sub id cannot be determined.
+ * <p>Type: INTEGER (long)</p>
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
+ * The identity of the sender of a sent message. It is
+ * usually the package name of the app which sends the message.
+ * <p class="note"><strong>Note:</strong>
+ * This column is read-only. It is set by the provider and can not be changed by apps.
+ * <p>Type: TEXT</p>
+ */
+ public static final String CREATOR = "creator";
+ }
+
+ /**
+ * Columns for the "canonical_addresses" table used by MMS and SMS.
+ */
+ public interface CanonicalAddressesColumns extends BaseColumns {
+ /**
+ * An address used in MMS or SMS. Email addresses are
+ * converted to lower case and are compared by string
+ * equality. Other addresses are compared using
+ * PHONE_NUMBERS_EQUAL.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADDRESS = "address";
+ }
+
+ /**
+ * Columns for the "threads" table used by MMS and SMS.
+ */
+ public interface ThreadsColumns extends BaseColumns {
+
+ /**
+ * The date at which the thread was created.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DATE = "date";
+
+ /**
+ * A string encoding of the recipient IDs of the recipients of
+ * the message, in numerical order and separated by spaces.
+ * <P>Type: TEXT</P>
+ */
+ public static final String RECIPIENT_IDS = "recipient_ids";
+
+ /**
+ * The message count of the thread.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_COUNT = "message_count";
+
+ /**
+ * Indicates whether all messages of the thread have been read.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String READ = "read";
+
+ /**
+ * The snippet of the latest message in the thread.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SNIPPET = "snippet";
+
+ /**
+ * The charset of the snippet.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SNIPPET_CHARSET = "snippet_cs";
+
+ /**
+ * Type of the thread, either {@link Threads#COMMON_THREAD} or
+ * {@link Threads#BROADCAST_THREAD}.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * Indicates whether there is a transmission error in the thread.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR = "error";
+
+ /**
+ * Indicates whether this thread contains any attachments.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String HAS_ATTACHMENT = "has_attachment";
+
+ /**
+ * If the thread is archived
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String ARCHIVED = "archived";
+ }
+
+ /**
+ * Helper functions for the "threads" table used by MMS and SMS.
+ */
+ public static final class Threads implements ThreadsColumns {
+
+ private static final String[] ID_PROJECTION = { BaseColumns._ID };
+
+ /**
+ * Private {@code content://} style URL for this table. Used by
+ * {@link #getOrCreateThreadId(android.content.Context, java.util.Set)}.
+ */
+ private static final Uri THREAD_ID_CONTENT_URI = Uri.parse(
+ "content://mms-sms/threadID");
+
+ /**
+ * The {@code content://} style URL for this table, by conversation.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ MmsSms.CONTENT_URI, "conversations");
+
+ /**
+ * The {@code content://} style URL for this table, for obsolete threads.
+ */
+ public static final Uri OBSOLETE_THREADS_URI = Uri.withAppendedPath(
+ CONTENT_URI, "obsolete");
+
+ /** Thread type: common thread. */
+ public static final int COMMON_THREAD = 0;
+
+ /** Thread type: broadcast thread. */
+ public static final int BROADCAST_THREAD = 1;
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Threads() {
+ }
+
+ /**
+ * This is a single-recipient version of {@code getOrCreateThreadId}.
+ * It's convenient for use with SMS messages.
+ * @param context the context object to use.
+ * @param recipient the recipient to send to.
+ */
+ public static long getOrCreateThreadId(Context context, String recipient) {
+ Set<String> recipients = new HashSet<String>();
+
+ recipients.add(recipient);
+ return getOrCreateThreadId(context, recipients);
+ }
+
+ /**
+ * Given the recipients list and subject of an unsaved message,
+ * return its thread ID. If the message starts a new thread,
+ * allocate a new thread ID. Otherwise, use the appropriate
+ * existing thread ID.
+ *
+ * <p>Find the thread ID of the same set of recipients (in any order,
+ * without any additions). If one is found, return it. Otherwise,
+ * return a unique thread ID.</p>
+ */
+ public static long getOrCreateThreadId(
+ Context context, Set<String> recipients) {
+ Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
+
+ for (String recipient : recipients) {
+ if (Mms.isEmailAddress(recipient)) {
+ recipient = Mms.extractAddrSpec(recipient);
+ }
+
+ uriBuilder.appendQueryParameter("recipient", recipient);
+ }
+
+ Uri uri = uriBuilder.build();
+ //if (DEBUG) Rlog.v(TAG, "getOrCreateThreadId uri: " + uri);
+
+ Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
+ uri, ID_PROJECTION, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(0);
+ } else {
+ Rlog.e(TAG, "getOrCreateThreadId returned no rows!");
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ Rlog.e(TAG, "getOrCreateThreadId failed with " + recipients.size() + " recipients");
+ throw new IllegalArgumentException("Unable to find or allocate a thread ID.");
+ }
+ }
+
+ /**
+ * Contains all MMS messages.
+ */
+ public static final class Mms implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Mms() {
+ }
+
+ /**
+ * The {@code content://} URI for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://mms");
+
+ /**
+ * Content URI for getting MMS report requests.
+ */
+ public static final Uri REPORT_REQUEST_URI = Uri.withAppendedPath(
+ CONTENT_URI, "report-request");
+
+ /**
+ * Content URI for getting MMS report status.
+ */
+ public static final Uri REPORT_STATUS_URI = Uri.withAppendedPath(
+ CONTENT_URI, "report-status");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+
+ /**
+ * Regex pattern for names and email addresses.
+ * <ul>
+ * <li><em>mailbox</em> = {@code name-addr}</li>
+ * <li><em>name-addr</em> = {@code [display-name] angle-addr}</li>
+ * <li><em>angle-addr</em> = {@code [CFWS] "<" addr-spec ">" [CFWS]}</li>
+ * </ul>
+ * @hide
+ */
+ public static final Pattern NAME_ADDR_EMAIL_PATTERN =
+ Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
+
+ /**
+ * Helper method to query this table.
+ * @hide
+ */
+ public static Cursor query(
+ ContentResolver cr, String[] projection) {
+ return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER);
+ }
+
+ /**
+ * Helper method to query this table.
+ * @hide
+ */
+ public static Cursor query(
+ ContentResolver cr, String[] projection,
+ String where, String orderBy) {
+ return cr.query(CONTENT_URI, projection,
+ where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ }
+
+ /**
+ * Helper method to extract email address from address string.
+ * @hide
+ */
+ public static String extractAddrSpec(String address) {
+ Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
+
+ if (match.matches()) {
+ return match.group(2);
+ }
+ return address;
+ }
+
+ /**
+ * Is the specified address an email address?
+ *
+ * @param address the input address to test
+ * @return true if address is an email address; false otherwise.
+ * @hide
+ */
+ public static boolean isEmailAddress(String address) {
+ if (TextUtils.isEmpty(address)) {
+ return false;
+ }
+
+ String s = extractAddrSpec(address);
+ Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
+ return match.matches();
+ }
+
+ /**
+ * Is the specified number a phone number?
+ *
+ * @param number the input number to test
+ * @return true if number is a phone number; false otherwise.
+ * @hide
+ */
+ public static boolean isPhoneNumber(String number) {
+ if (TextUtils.isEmpty(number)) {
+ return false;
+ }
+
+ Matcher match = Patterns.PHONE.matcher(number);
+ return match.matches();
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app inbox.
+ */
+ public static final class Inbox implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Inbox() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/inbox");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app sent folder.
+ */
+ public static final class Sent implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Sent() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/sent");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app drafts folder.
+ */
+ public static final class Draft implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Draft() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/drafts");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains all MMS messages in the MMS app outbox.
+ */
+ public static final class Outbox implements BaseMmsColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Outbox() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri
+ CONTENT_URI = Uri.parse("content://mms/outbox");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "date DESC";
+ }
+
+ /**
+ * Contains address information for an MMS message.
+ */
+ public static final class Addr implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Addr() {
+ }
+
+ /**
+ * The ID of MM which this address entry belongs to.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String MSG_ID = "msg_id";
+
+ /**
+ * The ID of contact entry in Phone Book.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String CONTACT_ID = "contact_id";
+
+ /**
+ * The address text.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ADDRESS = "address";
+
+ /**
+ * Type of address: must be one of {@code PduHeaders.BCC},
+ * {@code PduHeaders.CC}, {@code PduHeaders.FROM}, {@code PduHeaders.TO}.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * Character set of this entry (MMS charset value).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CHARSET = "charset";
+ }
+
+ /**
+ * Contains message parts.
+ */
+ public static final class Part implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Part() {
+ }
+
+ /**
+ * The identifier of the message which this part belongs to.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MSG_ID = "mid";
+
+ /**
+ * The order of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SEQ = "seq";
+
+ /**
+ * The content type of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_TYPE = "ct";
+
+ /**
+ * The name of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NAME = "name";
+
+ /**
+ * The charset of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CHARSET = "chset";
+
+ /**
+ * The file name of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String FILENAME = "fn";
+
+ /**
+ * The content disposition of the part.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CONTENT_DISPOSITION = "cd";
+
+ /**
+ * The content ID of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_ID = "cid";
+
+ /**
+ * The content location of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTENT_LOCATION = "cl";
+
+ /**
+ * The start of content-type of the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CT_START = "ctt_s";
+
+ /**
+ * The type of content-type of the message.
+ * <P>Type: TEXT</P>
+ */
+ public static final String CT_TYPE = "ctt_t";
+
+ /**
+ * The location (on filesystem) of the binary data of the part.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String _DATA = "_data";
+
+ /**
+ * The message text.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TEXT = "text";
+ }
+
+ /**
+ * Message send rate table.
+ */
+ public static final class Rate {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Rate() {
+ }
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ Mms.CONTENT_URI, "rate");
+
+ /**
+ * When a message was successfully sent.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String SENT_TIME = "sent_time";
+ }
+
+ /**
+ * Intents class.
+ */
+ public static final class Intents {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Intents() {
+ }
+
+ /**
+ * Indicates that the contents of specified URIs were changed.
+ * The application which is showing or caching these contents
+ * should be updated.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String CONTENT_CHANGED_ACTION
+ = "android.intent.action.CONTENT_CHANGED";
+
+ /**
+ * An extra field which stores the URI of deleted contents.
+ */
+ public static final String DELETED_CONTENTS = "deleted_contents";
+ }
+ }
+
+ /**
+ * Contains all MMS and SMS messages.
+ */
+ public static final class MmsSms implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private MmsSms() {
+ }
+
+ /**
+ * The column to distinguish SMS and MMS messages in query results.
+ */
+ public static final String TYPE_DISCRIMINATOR_COLUMN =
+ "transport_type";
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://mms-sms/");
+
+ /**
+ * The {@code content://} style URL for this table, by conversation.
+ */
+ public static final Uri CONTENT_CONVERSATIONS_URI = Uri.parse(
+ "content://mms-sms/conversations");
+
+ /**
+ * The {@code content://} style URL for this table, by phone number.
+ */
+ public static final Uri CONTENT_FILTER_BYPHONE_URI = Uri.parse(
+ "content://mms-sms/messages/byphone");
+
+ /**
+ * The {@code content://} style URL for undelivered messages in this table.
+ */
+ public static final Uri CONTENT_UNDELIVERED_URI = Uri.parse(
+ "content://mms-sms/undelivered");
+
+ /**
+ * The {@code content://} style URL for draft messages in this table.
+ */
+ public static final Uri CONTENT_DRAFT_URI = Uri.parse(
+ "content://mms-sms/draft");
+
+ /**
+ * The {@code content://} style URL for locked messages in this table.
+ */
+ public static final Uri CONTENT_LOCKED_URI = Uri.parse(
+ "content://mms-sms/locked");
+
+ /**
+ * Pass in a query parameter called "pattern" which is the text to search for.
+ * The sort order is fixed to be: {@code thread_id ASC, date DESC}.
+ */
+ public static final Uri SEARCH_URI = Uri.parse(
+ "content://mms-sms/search");
+
+ // Constants for message protocol types.
+
+ /** SMS protocol type. */
+ public static final int SMS_PROTO = 0;
+
+ /** MMS protocol type. */
+ public static final int MMS_PROTO = 1;
+
+ // Constants for error types of pending messages.
+
+ /** Error type: no error. */
+ public static final int NO_ERROR = 0;
+
+ /** Error type: generic transient error. */
+ public static final int ERR_TYPE_GENERIC = 1;
+
+ /** Error type: SMS protocol transient error. */
+ public static final int ERR_TYPE_SMS_PROTO_TRANSIENT = 2;
+
+ /** Error type: MMS protocol transient error. */
+ public static final int ERR_TYPE_MMS_PROTO_TRANSIENT = 3;
+
+ /** Error type: transport failure. */
+ public static final int ERR_TYPE_TRANSPORT_FAILURE = 4;
+
+ /** Error type: permanent error (along with all higher error values). */
+ public static final int ERR_TYPE_GENERIC_PERMANENT = 10;
+
+ /** Error type: SMS protocol permanent error. */
+ public static final int ERR_TYPE_SMS_PROTO_PERMANENT = 11;
+
+ /** Error type: MMS protocol permanent error. */
+ public static final int ERR_TYPE_MMS_PROTO_PERMANENT = 12;
+
+ /**
+ * Contains pending messages info.
+ */
+ public static final class PendingMessages implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private PendingMessages() {
+ }
+
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(
+ MmsSms.CONTENT_URI, "pending");
+
+ /**
+ * The type of transport protocol (MMS or SMS).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String PROTO_TYPE = "proto_type";
+
+ /**
+ * The ID of the message to be sent or downloaded.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String MSG_ID = "msg_id";
+
+ /**
+ * The type of the message to be sent or downloaded.
+ * This field is only valid for MM. For SM, its value is always set to 0.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MSG_TYPE = "msg_type";
+
+ /**
+ * The type of the error code.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_TYPE = "err_type";
+
+ /**
+ * The error code of sending/retrieving process.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ERROR_CODE = "err_code";
+
+ /**
+ * How many times we tried to send or download the message.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String RETRY_INDEX = "retry_index";
+
+ /**
+ * The time to do next retry.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DUE_TIME = "due_time";
+
+ /**
+ * The time we last tried to send or download the message.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String LAST_TRY = "last_try";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be
+ * < 0 if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ */
+ public static final String SUBSCRIPTION_ID = "pending_sub_id";
+ }
+
+ /**
+ * Words table used by provider for full-text searches.
+ * @hide
+ */
+ public static final class WordsTable {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private WordsTable() {}
+
+ /**
+ * Primary key.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String ID = "_id";
+
+ /**
+ * Source row ID.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String SOURCE_ROW_ID = "source_id";
+
+ /**
+ * Table ID (either 1 or 2).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String TABLE_ID = "table_to_use";
+
+ /**
+ * The words to index.
+ * <P>Type: TEXT</P>
+ */
+ public static final String INDEXED_TEXT = "index_text";
+ }
+ }
+
+ /**
+ * Carriers class contains information about APNs, including MMSC information.
+ */
+ public static final class Carriers implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private Carriers() {}
+
+ /**
+ * The {@code content://} style URL for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://telephony/carriers");
+
+ /**
+ * The default sort order for this table.
+ */
+ public static final String DEFAULT_SORT_ORDER = "name ASC";
+
+ /**
+ * Entry name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NAME = "name";
+
+ /**
+ * APN name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String APN = "apn";
+
+ /**
+ * Proxy address.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PROXY = "proxy";
+
+ /**
+ * Proxy port.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PORT = "port";
+
+ /**
+ * MMS proxy address.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MMSPROXY = "mmsproxy";
+
+ /**
+ * MMS proxy port.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MMSPORT = "mmsport";
+
+ /**
+ * Server address.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SERVER = "server";
+
+ /**
+ * APN username.
+ * <P>Type: TEXT</P>
+ */
+ public static final String USER = "user";
+
+ /**
+ * APN password.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PASSWORD = "password";
+
+ /**
+ * MMSC URL.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MMSC = "mmsc";
+
+ /**
+ * Mobile Country Code (MCC).
+ * <P>Type: TEXT</P>
+ */
+ public static final String MCC = "mcc";
+
+ /**
+ * Mobile Network Code (MNC).
+ * <P>Type: TEXT</P>
+ */
+ public static final String MNC = "mnc";
+
+ /**
+ * Numeric operator ID (as String). Usually {@code MCC + MNC}.
+ * <P>Type: TEXT</P>
+ */
+ public static final String NUMERIC = "numeric";
+
+ /**
+ * Authentication type.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String AUTH_TYPE = "authtype";
+
+ /**
+ * Comma-delimited list of APN types.
+ * <P>Type: TEXT</P>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The protocol to use to connect to this APN.
+ *
+ * One of the {@code PDP_type} values in TS 27.007 section 10.1.1.
+ * For example: {@code IP}, {@code IPV6}, {@code IPV4V6}, or {@code PPP}.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PROTOCOL = "protocol";
+
+ /**
+ * The protocol to use to connect to this APN when roaming.
+ * The syntax is the same as protocol.
+ * <P>Type: TEXT</P>
+ */
+ public static final String ROAMING_PROTOCOL = "roaming_protocol";
+
+ /**
+ * Is this the current APN?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String CURRENT = "current";
+
+ /**
+ * Is this APN enabled?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String CARRIER_ENABLED = "carrier_enabled";
+
+ /**
+ * Radio Access Technology info.
+ * To check what values are allowed, refer to {@link android.telephony.ServiceState}.
+ * This should be spread to other technologies,
+ * but is currently only used for LTE (14) and eHRPD (13).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String BEARER = "bearer";
+
+ /**
+ * Radio Access Technology bitmask.
+ * To check what values can be contained, refer to {@link android.telephony.ServiceState}.
+ * 0 indicates all techs otherwise first bit refers to RAT/bearer 1, second bit refers to
+ * RAT/bearer 2 and so on.
+ * Bitmask for a radio tech R is (1 << (R - 1))
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String BEARER_BITMASK = "bearer_bitmask";
+
+ /**
+ * MVNO type:
+ * {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MVNO_TYPE = "mvno_type";
+
+ /**
+ * MVNO data.
+ * Use the following examples.
+ * <ul>
+ * <li>SPN: A MOBILE, BEN NL, ...</li>
+ * <li>IMSI: 302720x94, 2060188, ...</li>
+ * <li>GID: 4E, 33, ...</li>
+ * </ul>
+ * <P>Type: TEXT</P>
+ */
+ public static final String MVNO_MATCH_DATA = "mvno_match_data";
+
+ /**
+ * The subscription to which the APN belongs to
+ * <p>Type: INTEGER (long) </p>
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
+ * The profile_id to which the APN saved in modem
+ * <p>Type: INTEGER</p>
+ *@hide
+ */
+ public static final String PROFILE_ID = "profile_id";
+
+ /**
+ * Is the apn setting to be set in modem
+ * <P>Type: INTEGER (boolean)</P>
+ *@hide
+ */
+ public static final String MODEM_COGNITIVE = "modem_cognitive";
+
+ /**
+ * The max connections of this apn
+ * <p>Type: INTEGER</p>
+ *@hide
+ */
+ public static final String MAX_CONNS = "max_conns";
+
+ /**
+ * The wait time for retry of the apn
+ * <p>Type: INTEGER</p>
+ *@hide
+ */
+ public static final String WAIT_TIME = "wait_time";
+
+ /**
+ * The time to limit max connection for the apn
+ * <p>Type: INTEGER</p>
+ *@hide
+ */
+ public static final String MAX_CONNS_TIME = "max_conns_time";
+
+ /**
+ * The MTU size of the mobile interface to which the APN connected
+ * <p>Type: INTEGER </p>
+ * @hide
+ */
+ public static final String MTU = "mtu";
+
+ /**
+ * Is this APN added/edited/deleted by a user or carrier?
+ * <p>Type: INTEGER </p>
+ * @hide
+ */
+ public static final String EDITED = "edited";
+
+ /**
+ * Is this APN visible to the user?
+ * <p>Type: INTEGER (boolean) </p>
+ * @hide
+ */
+ public static final String USER_VISIBLE = "user_visible";
+
+ /**
+ * Following are possible values for the EDITED field
+ * @hide
+ */
+ public static final int UNEDITED = 0;
+ /**
+ * @hide
+ */
+ public static final int USER_EDITED = 1;
+ /**
+ * @hide
+ */
+ public static final int USER_DELETED = 2;
+ /**
+ * DELETED_BUT_PRESENT is an intermediate value used to indicate that an entry deleted
+ * by the user is still present in the new APN database and therefore must remain tagged
+ * as user deleted rather than completely removed from the database
+ * @hide
+ */
+ public static final int USER_DELETED_BUT_PRESENT_IN_XML = 3;
+ /**
+ * @hide
+ */
+ public static final int CARRIER_EDITED = 4;
+ /**
+ * CARRIER_DELETED values are currently not used as there is no usecase. If they are used,
+ * delete() will have to change accordingly. Currently it is hardcoded to USER_DELETED.
+ * @hide
+ */
+ public static final int CARRIER_DELETED = 5;
+ /**
+ * @hide
+ */
+ public static final int CARRIER_DELETED_BUT_PRESENT_IN_XML = 6;
+ }
+
+ /**
+ * Contains received SMS cell broadcast messages.
+ * @hide
+ */
+ public static final class CellBroadcasts implements BaseColumns {
+
+ /**
+ * Not instantiable.
+ * @hide
+ */
+ private CellBroadcasts() {}
+
+ /**
+ * The {@code content://} URI for this table.
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://cellbroadcasts");
+
+ /**
+ * Message geographical scope.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String GEOGRAPHICAL_SCOPE = "geo_scope";
+
+ /**
+ * Message serial number.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SERIAL_NUMBER = "serial_number";
+
+ /**
+ * PLMN of broadcast sender. {@code SERIAL_NUMBER + PLMN + LAC + CID} uniquely identifies
+ * a broadcast for duplicate detection purposes.
+ * <P>Type: TEXT</P>
+ */
+ public static final String PLMN = "plmn";
+
+ /**
+ * Location Area (GSM) or Service Area (UMTS) of broadcast sender. Unused for CDMA.
+ * Only included if Geographical Scope of message is not PLMN wide (01).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String LAC = "lac";
+
+ /**
+ * Cell ID of message sender (GSM/UMTS). Unused for CDMA. Only included when the
+ * Geographical Scope of message is cell wide (00 or 11).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CID = "cid";
+
+ /**
+ * Message code. <em>OBSOLETE: merged into SERIAL_NUMBER.</em>
+ * <P>Type: INTEGER</P>
+ */
+ public static final String V1_MESSAGE_CODE = "message_code";
+
+ /**
+ * Message identifier. <em>OBSOLETE: renamed to SERVICE_CATEGORY.</em>
+ * <P>Type: INTEGER</P>
+ */
+ public static final String V1_MESSAGE_IDENTIFIER = "message_id";
+
+ /**
+ * Service category (GSM/UMTS: message identifier; CDMA: service category).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String SERVICE_CATEGORY = "service_category";
+
+ /**
+ * Message language code.
+ * <P>Type: TEXT</P>
+ */
+ public static final String LANGUAGE_CODE = "language";
+
+ /**
+ * Message body.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MESSAGE_BODY = "body";
+
+ /**
+ * Message delivery time.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String DELIVERY_TIME = "date";
+
+ /**
+ * Has the message been viewed?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String MESSAGE_READ = "read";
+
+ /**
+ * Message format (3GPP or 3GPP2).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_FORMAT = "format";
+
+ /**
+ * Message priority (including emergency).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MESSAGE_PRIORITY = "priority";
+
+ /**
+ * ETWS warning type (ETWS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String ETWS_WARNING_TYPE = "etws_warning_type";
+
+ /**
+ * CMAS message class (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_MESSAGE_CLASS = "cmas_message_class";
+
+ /**
+ * CMAS category (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_CATEGORY = "cmas_category";
+
+ /**
+ * CMAS response type (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_RESPONSE_TYPE = "cmas_response_type";
+
+ /**
+ * CMAS severity (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_SEVERITY = "cmas_severity";
+
+ /**
+ * CMAS urgency (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_URGENCY = "cmas_urgency";
+
+ /**
+ * CMAS certainty (CMAS alerts only).
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CMAS_CERTAINTY = "cmas_certainty";
+
+ /** The default sort order for this table. */
+ public static final String DEFAULT_SORT_ORDER = DELIVERY_TIME + " DESC";
+
+ /**
+ * Query columns for instantiating {@link android.telephony.CellBroadcastMessage} objects.
+ */
+ public static final String[] QUERY_COLUMNS = {
+ _ID,
+ GEOGRAPHICAL_SCOPE,
+ PLMN,
+ LAC,
+ CID,
+ SERIAL_NUMBER,
+ SERVICE_CATEGORY,
+ LANGUAGE_CODE,
+ MESSAGE_BODY,
+ DELIVERY_TIME,
+ MESSAGE_READ,
+ MESSAGE_FORMAT,
+ MESSAGE_PRIORITY,
+ ETWS_WARNING_TYPE,
+ CMAS_MESSAGE_CLASS,
+ CMAS_CATEGORY,
+ CMAS_RESPONSE_TYPE,
+ CMAS_SEVERITY,
+ CMAS_URGENCY,
+ CMAS_CERTAINTY
+ };
+ }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 932c276..b461fe9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -124,6 +124,20 @@
static final int NEVER_USE = 2;
}
+ /** The otaspMode passed to PhoneStateListener#onOtaspChanged */
+ /** @hide */
+ static public final int OTASP_UNINITIALIZED = 0;
+ /** @hide */
+ static public final int OTASP_UNKNOWN = 1;
+ /** @hide */
+ static public final int OTASP_NEEDED = 2;
+ /** @hide */
+ static public final int OTASP_NOT_NEEDED = 3;
+ /* OtaUtil has conflict enum 4: OtaUtils.OTASP_FAILURE_SPC_RETRIES */
+ /** @hide */
+ static public final int OTASP_SIM_UNPROVISIONED = 5;
+
+
private final Context mContext;
private final int mSubId;
private SubscriptionManager mSubscriptionManager;
diff --git a/telephony/java/com/android/internal/telephony/IccUtils.java b/telephony/java/com/android/internal/telephony/IccUtils.java
new file mode 100644
index 0000000..67de87f
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IccUtils.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2006 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.uicc;
+
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.GsmAlphabet;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Various methods, useful for dealing with SIM data.
+ */
+public class IccUtils {
+ static final String LOG_TAG="IccUtils";
+
+ /**
+ * Many fields in GSM SIM's are stored as nibble-swizzled BCD
+ *
+ * Assumes left-justified field that may be padded right with 0xf
+ * values.
+ *
+ * Stops on invalid BCD value, returning string so far
+ */
+ public static String
+ bcdToString(byte[] data, int offset, int length) {
+ StringBuilder ret = new StringBuilder(length*2);
+
+ for (int i = offset ; i < offset + length ; i++) {
+ int v;
+
+ v = data[i] & 0xf;
+ if (v > 9) break;
+ ret.append((char)('0' + v));
+
+ v = (data[i] >> 4) & 0xf;
+ // Some PLMNs have 'f' as high nibble, ignore it
+ if (v == 0xf) continue;
+ if (v > 9) break;
+ ret.append((char)('0' + v));
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * PLMN (MCC/MNC) is encoded as per 24.008 10.5.1.3
+ * Returns a concatenated string of MCC+MNC, stripping
+ * a trailing character for a 2-digit MNC
+ */
+ public static String bcdPlmnToString(byte[] data, int offset) {
+ if (offset + 3 > data.length) {
+ return null;
+ }
+ byte[] trans = new byte[3];
+ trans[0] = (byte) ((data[0 + offset] << 4) | ((data[0 + offset] >> 4) & 0xF));
+ trans[1] = (byte) ((data[1 + offset] << 4) | (data[2 + offset] & 0xF));
+ trans[2] = (byte) ((data[2 + offset] & 0xF0) | ((data[1 + offset] >> 4) & 0xF));
+ String ret = bytesToHexString(trans);
+
+ // For a 2-digit MNC we trim the trailing 'f'
+ if (ret.endsWith("f")) {
+ ret = ret.substring(0, ret.length() - 1);
+ }
+ return ret;
+ }
+
+ /**
+ * Some fields (like ICC ID) in GSM SIMs are stored as nibble-swizzled BCH
+ */
+ public static String
+ bchToString(byte[] data, int offset, int length) {
+ StringBuilder ret = new StringBuilder(length*2);
+
+ for (int i = offset ; i < offset + length ; i++) {
+ int v;
+
+ v = data[i] & 0xf;
+ ret.append("0123456789abcdef".charAt(v));
+
+ v = (data[i] >> 4) & 0xf;
+ ret.append("0123456789abcdef".charAt(v));
+ }
+
+ return ret.toString();
+ }
+
+ /**
+ * Decode cdma byte into String.
+ */
+ public static String
+ cdmaBcdToString(byte[] data, int offset, int length) {
+ StringBuilder ret = new StringBuilder(length);
+
+ int count = 0;
+ for (int i = offset; count < length; i++) {
+ int v;
+ v = data[i] & 0xf;
+ if (v > 9) v = 0;
+ ret.append((char)('0' + v));
+
+ if (++count == length) break;
+
+ v = (data[i] >> 4) & 0xf;
+ if (v > 9) v = 0;
+ ret.append((char)('0' + v));
+ ++count;
+ }
+ return ret.toString();
+ }
+
+ /**
+ * Decodes a GSM-style BCD byte, returning an int ranging from 0-99.
+ *
+ * In GSM land, the least significant BCD digit is stored in the most
+ * significant nibble.
+ *
+ * Out-of-range digits are treated as 0 for the sake of the time stamp,
+ * because of this:
+ *
+ * TS 23.040 section 9.2.3.11
+ * "if the MS receives a non-integer value in the SCTS, it shall
+ * assume the digit is set to 0 but shall store the entire field
+ * exactly as received"
+ */
+ public static int
+ gsmBcdByteToInt(byte b) {
+ int ret = 0;
+
+ // treat out-of-range BCD values as 0
+ if ((b & 0xf0) <= 0x90) {
+ ret = (b >> 4) & 0xf;
+ }
+
+ if ((b & 0x0f) <= 0x09) {
+ ret += (b & 0xf) * 10;
+ }
+
+ return ret;
+ }
+
+ /**
+ * Decodes a CDMA style BCD byte like {@link #gsmBcdByteToInt}, but
+ * opposite nibble format. The least significant BCD digit
+ * is in the least significant nibble and the most significant
+ * is in the most significant nibble.
+ */
+ public static int
+ cdmaBcdByteToInt(byte b) {
+ int ret = 0;
+
+ // treat out-of-range BCD values as 0
+ if ((b & 0xf0) <= 0x90) {
+ ret = ((b >> 4) & 0xf) * 10;
+ }
+
+ if ((b & 0x0f) <= 0x09) {
+ ret += (b & 0xf);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Decodes a string field that's formatted like the EF[ADN] alpha
+ * identifier
+ *
+ * From TS 51.011 10.5.1:
+ * Coding:
+ * this alpha tagging shall use either
+ * - the SMS default 7 bit coded alphabet as defined in
+ * TS 23.038 [12] with bit 8 set to 0. The alpha identifier
+ * shall be left justified. Unused bytes shall be set to 'FF'; or
+ * - one of the UCS2 coded options as defined in annex B.
+ *
+ * Annex B from TS 11.11 V8.13.0:
+ * 1) If the first octet in the alpha string is '80', then the
+ * remaining octets are 16 bit UCS2 characters ...
+ * 2) if the first octet in the alpha string is '81', then the
+ * second octet contains a value indicating the number of
+ * characters in the string, and the third octet contains an
+ * 8 bit number which defines bits 15 to 8 of a 16 bit
+ * base pointer, where bit 16 is set to zero and bits 7 to 1
+ * are also set to zero. These sixteen bits constitute a
+ * base pointer to a "half page" in the UCS2 code space, to be
+ * used with some or all of the remaining octets in the string.
+ * The fourth and subsequent octets contain codings as follows:
+ * If bit 8 of the octet is set to zero, the remaining 7 bits
+ * of the octet contain a GSM Default Alphabet character,
+ * whereas if bit 8 of the octet is set to one, then the
+ * remaining seven bits are an offset value added to the
+ * 16 bit base pointer defined earlier...
+ * 3) If the first octet of the alpha string is set to '82', then
+ * the second octet contains a value indicating the number of
+ * characters in the string, and the third and fourth octets
+ * contain a 16 bit number which defines the complete 16 bit
+ * base pointer to a "half page" in the UCS2 code space...
+ */
+ public static String
+ adnStringFieldToString(byte[] data, int offset, int length) {
+ if (length == 0) {
+ return "";
+ }
+ if (length >= 1) {
+ if (data[offset] == (byte) 0x80) {
+ int ucslen = (length - 1) / 2;
+ String ret = null;
+
+ try {
+ ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
+ } catch (UnsupportedEncodingException ex) {
+ Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException",
+ ex);
+ }
+
+ if (ret != null) {
+ // trim off trailing FFFF characters
+
+ ucslen = ret.length();
+ while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF')
+ ucslen--;
+
+ return ret.substring(0, ucslen);
+ }
+ }
+ }
+
+ boolean isucs2 = false;
+ char base = '\0';
+ int len = 0;
+
+ if (length >= 3 && data[offset] == (byte) 0x81) {
+ len = data[offset + 1] & 0xFF;
+ if (len > length - 3)
+ len = length - 3;
+
+ base = (char) ((data[offset + 2] & 0xFF) << 7);
+ offset += 3;
+ isucs2 = true;
+ } else if (length >= 4 && data[offset] == (byte) 0x82) {
+ len = data[offset + 1] & 0xFF;
+ if (len > length - 4)
+ len = length - 4;
+
+ base = (char) (((data[offset + 2] & 0xFF) << 8) |
+ (data[offset + 3] & 0xFF));
+ offset += 4;
+ isucs2 = true;
+ }
+
+ if (isucs2) {
+ StringBuilder ret = new StringBuilder();
+
+ while (len > 0) {
+ // UCS2 subset case
+
+ if (data[offset] < 0) {
+ ret.append((char) (base + (data[offset] & 0x7F)));
+ offset++;
+ len--;
+ }
+
+ // GSM character set case
+
+ int count = 0;
+ while (count < len && data[offset + count] >= 0)
+ count++;
+
+ ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
+ offset, count));
+
+ offset += count;
+ len -= count;
+ }
+
+ return ret.toString();
+ }
+
+ Resources resource = Resources.getSystem();
+ String defaultCharset = "";
+ try {
+ defaultCharset =
+ resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset);
+ } catch (NotFoundException e) {
+ // Ignore Exception and defaultCharset is set to a empty string.
+ }
+ return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim());
+ }
+
+ static int
+ hexCharToInt(char c) {
+ if (c >= '0' && c <= '9') return (c - '0');
+ if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
+ if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
+
+ throw new RuntimeException ("invalid hex char '" + c + "'");
+ }
+
+ /**
+ * Converts a hex String to a byte array.
+ *
+ * @param s A string of hexadecimal characters, must be an even number of
+ * chars long
+ *
+ * @return byte array representation
+ *
+ * @throws RuntimeException on invalid format
+ */
+ public static byte[]
+ hexStringToBytes(String s) {
+ byte[] ret;
+
+ if (s == null) return null;
+
+ int sz = s.length();
+
+ ret = new byte[sz/2];
+
+ for (int i=0 ; i <sz ; i+=2) {
+ ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4)
+ | hexCharToInt(s.charAt(i+1)));
+ }
+
+ return ret;
+ }
+
+
+ /**
+ * Converts a byte array into a String of hexadecimal characters.
+ *
+ * @param bytes an array of bytes
+ *
+ * @return hex string representation of bytes array
+ */
+ public static String
+ bytesToHexString(byte[] bytes) {
+ if (bytes == null) return null;
+
+ StringBuilder ret = new StringBuilder(2*bytes.length);
+
+ for (int i = 0 ; i < bytes.length ; i++) {
+ int b;
+
+ b = 0x0f & (bytes[i] >> 4);
+
+ ret.append("0123456789abcdef".charAt(b));
+
+ b = 0x0f & bytes[i];
+
+ ret.append("0123456789abcdef".charAt(b));
+ }
+
+ return ret.toString();
+ }
+
+
+ /**
+ * Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string
+ * "offset" points to "octet 3", the coding scheme byte
+ * empty string returned on decode error
+ */
+ public static String
+ networkNameToString(byte[] data, int offset, int length) {
+ String ret;
+
+ if ((data[offset] & 0x80) != 0x80 || length < 1) {
+ return "";
+ }
+
+ switch ((data[offset] >>> 4) & 0x7) {
+ case 0:
+ // SMS character set
+ int countSeptets;
+ int unusedBits = data[offset] & 7;
+ countSeptets = (((length - 1) * 8) - unusedBits) / 7 ;
+ ret = GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets);
+ break;
+ case 1:
+ // UCS2
+ try {
+ ret = new String(data,
+ offset + 1, length - 1, "utf-16");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Rlog.e(LOG_TAG,"implausible UnsupportedEncodingException", ex);
+ }
+ break;
+
+ // unsupported encoding
+ default:
+ ret = "";
+ break;
+ }
+
+ // "Add CI"
+ // "The MS should add the letters for the Country's Initials and
+ // a separator (e.g. a space) to the text string"
+
+ if ((data[offset] & 0x40) != 0) {
+ // FIXME(mkf) add country initials here
+
+ }
+
+ return ret;
+ }
+
+ /**
+ * Convert a TS 131.102 image instance of code scheme '11' into Bitmap
+ * @param data The raw data
+ * @param length The length of image body
+ * @return The bitmap
+ */
+ public static Bitmap parseToBnW(byte[] data, int length){
+ int valueIndex = 0;
+ int width = data[valueIndex++] & 0xFF;
+ int height = data[valueIndex++] & 0xFF;
+ int numOfPixels = width*height;
+
+ int[] pixels = new int[numOfPixels];
+
+ int pixelIndex = 0;
+ int bitIndex = 7;
+ byte currentByte = 0x00;
+ while (pixelIndex < numOfPixels) {
+ // reassign data and index for every byte (8 bits).
+ if (pixelIndex % 8 == 0) {
+ currentByte = data[valueIndex++];
+ bitIndex = 7;
+ }
+ pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01);
+ }
+
+ if (pixelIndex != numOfPixels) {
+ Rlog.e(LOG_TAG, "parse end and size error");
+ }
+ return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
+ }
+
+ private static int bitToRGB(int bit){
+ if(bit == 1){
+ return Color.WHITE;
+ } else {
+ return Color.BLACK;
+ }
+ }
+
+ /**
+ * a TS 131.102 image instance of code scheme '11' into color Bitmap
+ *
+ * @param data The raw data
+ * @param length the length of image body
+ * @param transparency with or without transparency
+ * @return The color bitmap
+ */
+ public static Bitmap parseToRGB(byte[] data, int length,
+ boolean transparency) {
+ int valueIndex = 0;
+ int width = data[valueIndex++] & 0xFF;
+ int height = data[valueIndex++] & 0xFF;
+ int bits = data[valueIndex++] & 0xFF;
+ int colorNumber = data[valueIndex++] & 0xFF;
+ int clutOffset = ((data[valueIndex++] & 0xFF) << 8)
+ | (data[valueIndex++] & 0xFF);
+
+ int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber);
+ if (true == transparency) {
+ colorIndexArray[colorNumber - 1] = Color.TRANSPARENT;
+ }
+
+ int[] resultArray = null;
+ if (0 == (8 % bits)) {
+ resultArray = mapTo2OrderBitColor(data, valueIndex,
+ (width * height), colorIndexArray, bits);
+ } else {
+ resultArray = mapToNon2OrderBitColor(data, valueIndex,
+ (width * height), colorIndexArray, bits);
+ }
+
+ return Bitmap.createBitmap(resultArray, width, height,
+ Bitmap.Config.RGB_565);
+ }
+
+ private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex,
+ int length, int[] colorArray, int bits) {
+ if (0 != (8 % bits)) {
+ Rlog.e(LOG_TAG, "not event number of color");
+ return mapToNon2OrderBitColor(data, valueIndex, length, colorArray,
+ bits);
+ }
+
+ int mask = 0x01;
+ switch (bits) {
+ case 1:
+ mask = 0x01;
+ break;
+ case 2:
+ mask = 0x03;
+ break;
+ case 4:
+ mask = 0x0F;
+ break;
+ case 8:
+ mask = 0xFF;
+ break;
+ }
+
+ int[] resultArray = new int[length];
+ int resultIndex = 0;
+ int run = 8 / bits;
+ while (resultIndex < length) {
+ byte tempByte = data[valueIndex++];
+ for (int runIndex = 0; runIndex < run; ++runIndex) {
+ int offset = run - runIndex - 1;
+ resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits))
+ & mask];
+ }
+ }
+ return resultArray;
+ }
+
+ private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex,
+ int length, int[] colorArray, int bits) {
+ if (0 == (8 % bits)) {
+ Rlog.e(LOG_TAG, "not odd number of color");
+ return mapTo2OrderBitColor(data, valueIndex, length, colorArray,
+ bits);
+ }
+
+ int[] resultArray = new int[length];
+ // TODO fix me:
+ return resultArray;
+ }
+
+ private static int[] getCLUT(byte[] rawData, int offset, int number) {
+ if (null == rawData) {
+ return null;
+ }
+
+ int[] result = new int[number];
+ int endIndex = offset + (number * 3); // 1 color use 3 bytes
+ int valueIndex = offset;
+ int colorIndex = 0;
+ int alpha = 0xff << 24;
+ do {
+ result[colorIndex++] = alpha
+ | ((rawData[valueIndex++] & 0xFF) << 16)
+ | ((rawData[valueIndex++] & 0xFF) << 8)
+ | ((rawData[valueIndex++] & 0xFF));
+ } while (valueIndex < endIndex);
+ return result;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
new file mode 100644
index 0000000..f7f0f29
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/PhoneConstantConversions.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2006 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;
+
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.PreciseCallState;
+
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.List;
+
+public class PhoneConstantConversions {
+ /**
+ * Convert the {@link PhoneConstants.State} enum into the TelephonyManager.CALL_STATE_*
+ * constants for the public API.
+ */
+ public static int convertCallState(PhoneConstants.State state) {
+ switch (state) {
+ case RINGING:
+ return TelephonyManager.CALL_STATE_RINGING;
+ case OFFHOOK:
+ return TelephonyManager.CALL_STATE_OFFHOOK;
+ default:
+ return TelephonyManager.CALL_STATE_IDLE;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.CALL_STATE_* constants into the
+ * {@link PhoneConstants.State} enum for the public API.
+ */
+ public static PhoneConstants.State convertCallState(int state) {
+ switch (state) {
+ case TelephonyManager.CALL_STATE_RINGING:
+ return PhoneConstants.State.RINGING;
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ return PhoneConstants.State.OFFHOOK;
+ default:
+ return PhoneConstants.State.IDLE;
+ }
+ }
+
+ /**
+ * Convert the {@link PhoneConstants.DataState} enum into the TelephonyManager.DATA_* constants
+ * for the public API.
+ */
+ public static int convertDataState(PhoneConstants.DataState state) {
+ switch (state) {
+ case CONNECTING:
+ return TelephonyManager.DATA_CONNECTING;
+ case CONNECTED:
+ return TelephonyManager.DATA_CONNECTED;
+ case SUSPENDED:
+ return TelephonyManager.DATA_SUSPENDED;
+ default:
+ return TelephonyManager.DATA_DISCONNECTED;
+ }
+ }
+
+ /**
+ * Convert the TelephonyManager.DATA_* constants into {@link PhoneConstants.DataState} enum
+ * for the public API.
+ */
+ public static PhoneConstants.DataState convertDataState(int state) {
+ switch (state) {
+ case TelephonyManager.DATA_CONNECTING:
+ return PhoneConstants.DataState.CONNECTING;
+ case TelephonyManager.DATA_CONNECTED:
+ return PhoneConstants.DataState.CONNECTED;
+ case TelephonyManager.DATA_SUSPENDED:
+ return PhoneConstants.DataState.SUSPENDED;
+ default:
+ return PhoneConstants.DataState.DISCONNECTED;
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
new file mode 100644
index 0000000..439eaea
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.telephony.Rlog;
+import android.os.Build;
+import android.util.SparseIntArray;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.telephony.cdma.sms.UserData;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+public class Sms7BitEncodingTranslator {
+ private static final String TAG = "Sms7BitEncodingTranslator";
+ private static final boolean DBG = Build.IS_DEBUGGABLE ;
+ private static boolean mIs7BitTranslationTableLoaded = false;
+ private static SparseIntArray mTranslationTable = null;
+ private static SparseIntArray mTranslationTableCommon = null;
+ private static SparseIntArray mTranslationTableGSM = null;
+ private static SparseIntArray mTranslationTableCDMA = null;
+
+ // Parser variables
+ private static final String XML_START_TAG = "SmsEnforce7BitTranslationTable";
+ private static final String XML_TRANSLATION_TYPE_TAG = "TranslationType";
+ private static final String XML_CHARACTOR_TAG = "Character";
+ private static final String XML_FROM_TAG = "from";
+ private static final String XML_TO_TAG = "to";
+
+ /**
+ * Translates each message character that is not supported by GSM 7bit
+ * alphabet into a supported one
+ *
+ * @param message
+ * message to be translated
+ * @param throwsException
+ * if true and some error occurs during translation, an exception
+ * is thrown; otherwise a null String is returned
+ * @return translated message or null if some error occur
+ */
+ public static String translate(CharSequence message) {
+ if (message == null) {
+ Rlog.w(TAG, "Null message can not be translated");
+ return null;
+ }
+
+ int size = message.length();
+ if (size <= 0) {
+ return "";
+ }
+
+ if (!mIs7BitTranslationTableLoaded) {
+ mTranslationTableCommon = new SparseIntArray();
+ mTranslationTableGSM = new SparseIntArray();
+ mTranslationTableCDMA = new SparseIntArray();
+ load7BitTranslationTableFromXml();
+ mIs7BitTranslationTableLoaded = true;
+ }
+
+ if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) ||
+ (mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) ||
+ (mTranslationTableCDMA != null && mTranslationTableCDMA.size() > 0)) {
+ char[] output = new char[size];
+ boolean isCdmaFormat = useCdmaFormatForMoSms();
+ for (int i = 0; i < size; i++) {
+ output[i] = translateIfNeeded(message.charAt(i), isCdmaFormat);
+ }
+
+ return String.valueOf(output);
+ }
+
+ return null;
+ }
+
+ /**
+ * Translates a single character into its corresponding acceptable one, if
+ * needed, based on GSM 7-bit alphabet
+ *
+ * @param c
+ * character to be translated
+ * @return original character, if it's present on GSM 7-bit alphabet; a
+ * corresponding character, based on the translation table or white
+ * space, if no mapping is found in the translation table for such
+ * character
+ */
+ private static char translateIfNeeded(char c, boolean isCdmaFormat) {
+ if (noTranslationNeeded(c, isCdmaFormat)) {
+ if (DBG) {
+ Rlog.v(TAG, "No translation needed for " + Integer.toHexString(c));
+ }
+ return c;
+ }
+
+ /*
+ * Trying to translate unicode to Gsm 7-bit alphabet; If c is not
+ * present on translation table, c does not belong to Unicode Latin-1
+ * (Basic + Supplement), so we don't know how to translate it to a Gsm
+ * 7-bit character! We replace c for an empty space and advises the user
+ * about it.
+ */
+ int translation = -1;
+
+ if (mTranslationTableCommon != null) {
+ translation = mTranslationTableCommon.get(c, -1);
+ }
+
+ if (translation == -1) {
+ if (isCdmaFormat) {
+ if (mTranslationTableCDMA != null) {
+ translation = mTranslationTableCDMA.get(c, -1);
+ }
+ } else {
+ if (mTranslationTableGSM != null) {
+ translation = mTranslationTableGSM.get(c, -1);
+ }
+ }
+ }
+
+ if (translation != -1) {
+ if (DBG) {
+ Rlog.v(TAG, Integer.toHexString(c) + " (" + c + ")" + " translated to "
+ + Integer.toHexString(translation) + " (" + (char) translation + ")");
+ }
+ return (char) translation;
+ } else {
+ if (DBG) {
+ Rlog.w(TAG, "No translation found for " + Integer.toHexString(c)
+ + "! Replacing for empty space");
+ }
+ return ' ';
+ }
+ }
+
+ private static boolean noTranslationNeeded(char c, boolean isCdmaFormat) {
+ if (isCdmaFormat) {
+ return GsmAlphabet.isGsmSeptets(c) && UserData.charToAscii.get(c, -1) != -1;
+ }
+ else {
+ return GsmAlphabet.isGsmSeptets(c);
+ }
+ }
+
+ private static boolean useCdmaFormatForMoSms() {
+ if (!SmsManager.getDefault().isImsSmsSupported()) {
+ // use Voice technology to determine SMS format.
+ return TelephonyManager.getDefault().getCurrentPhoneType()
+ == PhoneConstants.PHONE_TYPE_CDMA;
+ }
+ // IMS is registered with SMS support, check the SMS format supported
+ return (SmsConstants.FORMAT_3GPP2.equals(SmsManager.getDefault().getImsSmsFormat()));
+ }
+
+ /**
+ * Load the whole translation table file from the framework resource
+ * encoded in XML.
+ */
+ private static void load7BitTranslationTableFromXml() {
+ XmlResourceParser parser = null;
+ Resources r = Resources.getSystem();
+
+ if (parser == null) {
+ if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: open normal file");
+ parser = r.getXml(com.android.internal.R.xml.sms_7bit_translation_table);
+ }
+
+ try {
+ XmlUtils.beginDocument(parser, XML_START_TAG);
+ while (true) {
+ XmlUtils.nextElement(parser);
+ String tag = parser.getName();
+ if (DBG) {
+ Rlog.d(TAG, "tag: " + tag);
+ }
+ if (XML_TRANSLATION_TYPE_TAG.equals(tag)) {
+ String type = parser.getAttributeValue(null, "Type");
+ if (DBG) {
+ Rlog.d(TAG, "type: " + type);
+ }
+ if (type.equals("common")) {
+ mTranslationTable = mTranslationTableCommon;
+ } else if (type.equals("gsm")) {
+ mTranslationTable = mTranslationTableGSM;
+ } else if (type.equals("cdma")) {
+ mTranslationTable = mTranslationTableCDMA;
+ } else {
+ Rlog.e(TAG, "Error Parsing 7BitTranslationTable: found incorrect type" + type);
+ }
+ } else if (XML_CHARACTOR_TAG.equals(tag) && mTranslationTable != null) {
+ int from = parser.getAttributeUnsignedIntValue(null,
+ XML_FROM_TAG, -1);
+ int to = parser.getAttributeUnsignedIntValue(null,
+ XML_TO_TAG, -1);
+ if ((from != -1) && (to != -1)) {
+ if (DBG) {
+ Rlog.d(TAG, "Loading mapping " + Integer.toHexString(from)
+ .toUpperCase() + " -> " + Integer.toHexString(to)
+ .toUpperCase());
+ }
+ mTranslationTable.put (from, to);
+ } else {
+ Rlog.d(TAG, "Invalid translation table file format");
+ }
+ } else {
+ break;
+ }
+ }
+ if (DBG) Rlog.d(TAG, "load7BitTranslationTableFromXml: parsing successful, file loaded");
+ } catch (Exception e) {
+ Rlog.e(TAG, "Got exception while loading 7BitTranslationTable file.", e);
+ } finally {
+ if (parser instanceof XmlResourceParser) {
+ ((XmlResourceParser)parser).close();
+ }
+ }
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsAddress.java b/telephony/java/com/android/internal/telephony/SmsAddress.java
new file mode 100644
index 0000000..b3892cb
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsAddress.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2008 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;
+
+public abstract class SmsAddress {
+ // From TS 23.040 9.1.2.5 and TS 24.008 table 10.5.118
+ // and C.S0005-D table 2.7.1.3.2.4-2
+ public static final int TON_UNKNOWN = 0;
+ public static final int TON_INTERNATIONAL = 1;
+ public static final int TON_NATIONAL = 2;
+ public static final int TON_NETWORK = 3;
+ public static final int TON_SUBSCRIBER = 4;
+ public static final int TON_ALPHANUMERIC = 5;
+ public static final int TON_ABBREVIATED = 6;
+
+ public int ton;
+ public String address;
+ public byte[] origBytes;
+
+ /**
+ * Returns the address of the SMS message in String form or null if unavailable
+ */
+ public String getAddressString() {
+ return address;
+ }
+
+ /**
+ * Returns true if this is an alphanumeric address
+ */
+ public boolean isAlphanumeric() {
+ return ton == TON_ALPHANUMERIC;
+ }
+
+ /**
+ * Returns true if this is a network address
+ */
+ public boolean isNetworkSpecific() {
+ return ton == TON_NETWORK;
+ }
+
+ public boolean couldBeEmailGateway() {
+ // Some carriers seems to send email gateway messages in this form:
+ // from: an UNKNOWN TON, 3 or 4 digits long, beginning with a 5
+ // PID: 0x00, Data coding scheme 0x03
+ // So we just attempt to treat any message from an address length <= 4
+ // as an email gateway
+
+ return address.length() <= 4;
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/java/com/android/internal/telephony/SmsApplication.java
new file mode 100644
index 0000000..d8ef429
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsApplication.java
@@ -0,0 +1,980 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.Manifest.permission;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.Rlog;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Class for managing the primary application that we will deliver SMS/MMS messages to
+ *
+ * {@hide}
+ */
+public final class SmsApplication {
+ static final String LOG_TAG = "SmsApplication";
+ private static final String PHONE_PACKAGE_NAME = "com.android.phone";
+ private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
+ private static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service";
+ private static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony";
+
+ private static final String SCHEME_SMS = "sms";
+ private static final String SCHEME_SMSTO = "smsto";
+ private static final String SCHEME_MMS = "mms";
+ private static final String SCHEME_MMSTO = "mmsto";
+ private static final boolean DEBUG_MULTIUSER = false;
+
+ private static SmsPackageMonitor sSmsPackageMonitor = null;
+
+ public static class SmsApplicationData {
+ /**
+ * Name of this SMS app for display.
+ */
+ private String mApplicationName;
+
+ /**
+ * Package name for this SMS app.
+ */
+ public String mPackageName;
+
+ /**
+ * The class name of the SMS_DELIVER_ACTION receiver in this app.
+ */
+ private String mSmsReceiverClass;
+
+ /**
+ * The class name of the WAP_PUSH_DELIVER_ACTION receiver in this app.
+ */
+ private String mMmsReceiverClass;
+
+ /**
+ * The class name of the ACTION_RESPOND_VIA_MESSAGE intent in this app.
+ */
+ private String mRespondViaMessageClass;
+
+ /**
+ * The class name of the ACTION_SENDTO intent in this app.
+ */
+ private String mSendToClass;
+
+ /**
+ * The class name of the ACTION_DEFAULT_SMS_PACKAGE_CHANGED receiver in this app.
+ */
+ private String mSmsAppChangedReceiverClass;
+
+ /**
+ * The class name of the ACTION_EXTERNAL_PROVIDER_CHANGE receiver in this app.
+ */
+ private String mProviderChangedReceiverClass;
+
+ /**
+ * The class name of the SIM_FULL_ACTION receiver in this app.
+ */
+ private String mSimFullReceiverClass;
+
+ /**
+ * The user-id for this application
+ */
+ private int mUid;
+
+ /**
+ * Returns true if this SmsApplicationData is complete (all intents handled).
+ * @return
+ */
+ public boolean isComplete() {
+ return (mSmsReceiverClass != null && mMmsReceiverClass != null
+ && mRespondViaMessageClass != null && mSendToClass != null);
+ }
+
+ public SmsApplicationData(String packageName, int uid) {
+ mPackageName = packageName;
+ mUid = uid;
+ }
+
+ public String getApplicationName(Context context) {
+ if (mApplicationName == null) {
+ PackageManager pm = context.getPackageManager();
+ ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfoAsUser(mPackageName, 0,
+ UserHandle.getUserId(mUid));
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ if (appInfo != null) {
+ CharSequence label = pm.getApplicationLabel(appInfo);
+ mApplicationName = (label == null) ? null : label.toString();
+ }
+ }
+ return mApplicationName;
+ }
+
+ @Override
+ public String toString() {
+ return " mPackageName: " + mPackageName
+ + " mSmsReceiverClass: " + mSmsReceiverClass
+ + " mMmsReceiverClass: " + mMmsReceiverClass
+ + " mRespondViaMessageClass: " + mRespondViaMessageClass
+ + " mSendToClass: " + mSendToClass
+ + " mSmsAppChangedClass: " + mSmsAppChangedReceiverClass
+ + " mProviderChangedReceiverClass: " + mProviderChangedReceiverClass
+ + " mSimFullReceiverClass: " + mSimFullReceiverClass
+ + " mUid: " + mUid;
+ }
+ }
+
+ /**
+ * Returns the userId of the Context object, if called from a system app,
+ * otherwise it returns the caller's userId
+ * @param context The context object passed in by the caller.
+ * @return
+ */
+ private static int getIncomingUserId(Context context) {
+ int contextUserId = context.getUserId();
+ final int callingUid = Binder.getCallingUid();
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getIncomingUserHandle caller=" + callingUid + ", myuid="
+ + android.os.Process.myUid() + "\n\t" + Debug.getCallers(4));
+ }
+ if (UserHandle.getAppId(callingUid)
+ < android.os.Process.FIRST_APPLICATION_UID) {
+ return contextUserId;
+ } else {
+ return UserHandle.getUserId(callingUid);
+ }
+ }
+
+ /**
+ * Returns the list of available SMS apps defined as apps that are registered for both the
+ * SMS_RECEIVED_ACTION (SMS) and WAP_PUSH_RECEIVED_ACTION (MMS) broadcasts (and their broadcast
+ * receivers are enabled)
+ *
+ * Requirements to be an SMS application:
+ * Implement SMS_DELIVER_ACTION broadcast receiver.
+ * Require BROADCAST_SMS permission.
+ *
+ * Implement WAP_PUSH_DELIVER_ACTION broadcast receiver.
+ * Require BROADCAST_WAP_PUSH permission.
+ *
+ * Implement RESPOND_VIA_MESSAGE intent.
+ * Support smsto Uri scheme.
+ * Require SEND_RESPOND_VIA_MESSAGE permission.
+ *
+ * Implement ACTION_SENDTO intent.
+ * Support smsto Uri scheme.
+ */
+ public static Collection<SmsApplicationData> getApplicationCollection(Context context) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getApplicationCollectionInternal(context, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private static Collection<SmsApplicationData> getApplicationCollectionInternal(
+ Context context, int userId) {
+ PackageManager packageManager = context.getPackageManager();
+
+ // Get the list of apps registered for SMS
+ Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
+ List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+ userId);
+
+ HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
+
+ // Add one entry to the map for every sms receiver (ignoring duplicate sms receivers)
+ for (ResolveInfo resolveInfo : smsReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ if (!permission.BROADCAST_SMS.equals(activityInfo.permission)) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ if (!receivers.containsKey(packageName)) {
+ final SmsApplicationData smsApplicationData = new SmsApplicationData(packageName,
+ activityInfo.applicationInfo.uid);
+ smsApplicationData.mSmsReceiverClass = activityInfo.name;
+ receivers.put(packageName, smsApplicationData);
+ }
+ }
+
+ // Update any existing entries with mms receiver class
+ intent = new Intent(Intents.WAP_PUSH_DELIVER_ACTION);
+ intent.setDataAndType(null, "application/vnd.wap.mms-message");
+ List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent, 0,
+ userId);
+ for (ResolveInfo resolveInfo : mmsReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ if (!permission.BROADCAST_WAP_PUSH.equals(activityInfo.permission)) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (smsApplicationData != null) {
+ smsApplicationData.mMmsReceiverClass = activityInfo.name;
+ }
+ }
+
+ // Update any existing entries with respond via message intent class.
+ intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE,
+ Uri.fromParts(SCHEME_SMSTO, "", null));
+ List<ResolveInfo> respondServices = packageManager.queryIntentServicesAsUser(intent, 0,
+ userId);
+ for (ResolveInfo resolveInfo : respondServices) {
+ final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (serviceInfo == null) {
+ continue;
+ }
+ if (!permission.SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
+ continue;
+ }
+ final String packageName = serviceInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (smsApplicationData != null) {
+ smsApplicationData.mRespondViaMessageClass = serviceInfo.name;
+ }
+ }
+
+ // Update any existing entries with supports send to.
+ intent = new Intent(Intent.ACTION_SENDTO,
+ Uri.fromParts(SCHEME_SMSTO, "", null));
+ List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent, 0,
+ userId);
+ for (ResolveInfo resolveInfo : sendToActivities) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (smsApplicationData != null) {
+ smsApplicationData.mSendToClass = activityInfo.name;
+ }
+ }
+
+ // Update any existing entries with the default sms changed handler.
+ intent = new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+ List<ResolveInfo> smsAppChangedReceivers =
+ packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" +
+ smsAppChangedReceivers);
+ }
+ for (ResolveInfo resolveInfo : smsAppChangedReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" +
+ packageName + " smsApplicationData: " + smsApplicationData +
+ " activityInfo.name: " + activityInfo.name);
+ }
+ if (smsApplicationData != null) {
+ smsApplicationData.mSmsAppChangedReceiverClass = activityInfo.name;
+ }
+ }
+
+ // Update any existing entries with the external provider changed handler.
+ intent = new Intent(Telephony.Sms.Intents.ACTION_EXTERNAL_PROVIDER_CHANGE);
+ List<ResolveInfo> providerChangedReceivers =
+ packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" +
+ providerChangedReceivers);
+ }
+ for (ResolveInfo resolveInfo : providerChangedReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal packageName=" +
+ packageName + " smsApplicationData: " + smsApplicationData +
+ " activityInfo.name: " + activityInfo.name);
+ }
+ if (smsApplicationData != null) {
+ smsApplicationData.mProviderChangedReceiverClass = activityInfo.name;
+ }
+ }
+
+ // Update any existing entries with the sim full handler.
+ intent = new Intent(Intents.SIM_FULL_ACTION);
+ List<ResolveInfo> simFullReceivers =
+ packageManager.queryBroadcastReceiversAsUser(intent, 0, userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers="
+ + simFullReceivers);
+ }
+ for (ResolveInfo resolveInfo : simFullReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplicationCollectionInternal packageName="
+ + packageName + " smsApplicationData: " + smsApplicationData
+ + " activityInfo.name: " + activityInfo.name);
+ }
+ if (smsApplicationData != null) {
+ smsApplicationData.mSimFullReceiverClass = activityInfo.name;
+ }
+ }
+
+ // Remove any entries for which we did not find all required intents.
+ for (ResolveInfo resolveInfo : smsReceivers) {
+ final ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ continue;
+ }
+ final String packageName = activityInfo.packageName;
+ final SmsApplicationData smsApplicationData = receivers.get(packageName);
+ if (smsApplicationData != null) {
+ if (!smsApplicationData.isComplete()) {
+ receivers.remove(packageName);
+ }
+ }
+ }
+ return receivers.values();
+ }
+
+ /**
+ * Checks to see if we have a valid installed SMS application for the specified package name
+ * @return Data for the specified package name or null if there isn't one
+ */
+ private static SmsApplicationData getApplicationForPackage(
+ Collection<SmsApplicationData> applications, String packageName) {
+ if (packageName == null) {
+ return null;
+ }
+ // Is there an entry in the application list for the specified package?
+ for (SmsApplicationData application : applications) {
+ if (application.mPackageName.contentEquals(packageName)) {
+ return application;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the application we will use for delivering SMS/MMS messages.
+ *
+ * We return the preferred sms application with the following order of preference:
+ * (1) User selected SMS app (if selected, and if still valid)
+ * (2) Android Messaging (if installed)
+ * (3) The currently configured highest priority broadcast receiver
+ * (4) Null
+ */
+ private static SmsApplicationData getApplication(Context context, boolean updateIfNeeded,
+ int userId) {
+ TelephonyManager tm = (TelephonyManager)
+ context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (!tm.isSmsCapable()) {
+ // No phone, no SMS
+ return null;
+ }
+
+ Collection<SmsApplicationData> applications = getApplicationCollectionInternal(context,
+ userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplication userId=" + userId);
+ }
+ // Determine which application receives the broadcast
+ String defaultApplication = Settings.Secure.getStringForUser(context.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplication defaultApp=" + defaultApplication);
+ }
+
+ SmsApplicationData applicationData = null;
+ if (defaultApplication != null) {
+ applicationData = getApplicationForPackage(applications, defaultApplication);
+ }
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplication appData=" + applicationData);
+ }
+ // Picking a new SMS app requires AppOps and Settings.Secure permissions, so we only do
+ // this if the caller asked us to.
+ if (updateIfNeeded && applicationData == null) {
+ // Try to find the default SMS package for this device
+ Resources r = context.getResources();
+ String defaultPackage =
+ r.getString(com.android.internal.R.string.default_sms_application);
+ applicationData = getApplicationForPackage(applications, defaultPackage);
+
+ if (applicationData == null) {
+ // Are there any applications?
+ if (applications.size() != 0) {
+ applicationData = (SmsApplicationData)applications.toArray()[0];
+ }
+ }
+
+ // If we found a new default app, update the setting
+ if (applicationData != null) {
+ setDefaultApplicationInternal(applicationData.mPackageName, context, userId);
+ }
+ }
+
+ // If we found a package, make sure AppOps permissions are set up correctly
+ if (applicationData != null) {
+ AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+
+ // We can only call checkOp if we are privileged (updateIfNeeded) or if the app we
+ // are checking is for our current uid. Doing this check from the unprivileged current
+ // SMS app allows us to tell the current SMS app that it is not in a good state and
+ // needs to ask to be the current SMS app again to work properly.
+ if (updateIfNeeded || applicationData.mUid == android.os.Process.myUid()) {
+ // Verify that the SMS app has permissions
+ int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+ applicationData.mPackageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ Rlog.e(LOG_TAG, applicationData.mPackageName + " lost OP_WRITE_SMS: " +
+ (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
+ if (updateIfNeeded) {
+ appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+ applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
+ } else {
+ // We can not return a package if permissions are not set up correctly
+ applicationData = null;
+ }
+ }
+ }
+
+ // We can only verify the phone and BT app's permissions from a privileged caller
+ if (updateIfNeeded) {
+ // Ensure this component is still configured as the preferred activity. Usually the
+ // current SMS app will already be the preferred activity - but checking whether or
+ // not this is true is just as expensive as reconfiguring the preferred activity so
+ // we just reconfigure every time.
+ PackageManager packageManager = context.getPackageManager();
+ configurePreferredActivity(packageManager, new ComponentName(
+ applicationData.mPackageName, applicationData.mSendToClass),
+ userId);
+ // Assign permission to special system apps
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ PHONE_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ BLUETOOTH_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ MMS_SERVICE_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ TELEPHONY_PROVIDER_PACKAGE_NAME);
+ // Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
+ // apps, all of them should be able to write to telephony provider.
+ // This is to allow the proxy package permission check in telephony provider
+ // to pass.
+ assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
+ }
+ }
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "getApplication returning appData=" + applicationData);
+ }
+ return applicationData;
+ }
+
+ /**
+ * Sets the specified package as the default SMS/MMS application. The caller of this method
+ * needs to have permission to set AppOps and write to secure settings.
+ */
+ public static void setDefaultApplication(String packageName, Context context) {
+ TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
+ if (!tm.isSmsCapable()) {
+ // No phone, no SMS
+ return;
+ }
+
+ final int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ setDefaultApplicationInternal(packageName, context, userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private static void setDefaultApplicationInternal(String packageName, Context context,
+ int userId) {
+ // Get old package name
+ String oldPackageName = Settings.Secure.getStringForUser(context.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldPackageName +
+ " new=" + packageName);
+ }
+
+ if (packageName != null && oldPackageName != null && packageName.equals(oldPackageName)) {
+ // No change
+ return;
+ }
+
+ // We only make the change if the new package is valid
+ PackageManager packageManager = context.getPackageManager();
+ Collection<SmsApplicationData> applications = getApplicationCollection(context);
+ SmsApplicationData oldAppData = oldPackageName != null ?
+ getApplicationForPackage(applications, oldPackageName) : null;
+ SmsApplicationData applicationData = getApplicationForPackage(applications, packageName);
+ if (applicationData != null) {
+ // Ignore OP_WRITE_SMS for the previously configured default SMS app.
+ AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+ if (oldPackageName != null) {
+ try {
+ PackageInfo info = packageManager.getPackageInfoAsUser(oldPackageName,
+ 0, userId);
+ appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+ oldPackageName, AppOpsManager.MODE_IGNORED);
+ } catch (NameNotFoundException e) {
+ Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
+ }
+ }
+
+ // Update the secure setting.
+ Settings.Secure.putStringForUser(context.getContentResolver(),
+ Settings.Secure.SMS_DEFAULT_APPLICATION, applicationData.mPackageName,
+ userId);
+
+ // Configure this as the preferred activity for SENDTO sms/mms intents
+ configurePreferredActivity(packageManager, new ComponentName(
+ applicationData.mPackageName, applicationData.mSendToClass), userId);
+
+ // Allow OP_WRITE_SMS for the newly configured default SMS app.
+ appOps.setMode(AppOpsManager.OP_WRITE_SMS, applicationData.mUid,
+ applicationData.mPackageName, AppOpsManager.MODE_ALLOWED);
+
+ // Assign permission to special system apps
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ PHONE_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ BLUETOOTH_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ MMS_SERVICE_PACKAGE_NAME);
+ assignWriteSmsPermissionToSystemApp(context, packageManager, appOps,
+ TELEPHONY_PROVIDER_PACKAGE_NAME);
+ // Give WRITE_SMS AppOps permission to UID 1001 which contains multiple
+ // apps, all of them should be able to write to telephony provider.
+ // This is to allow the proxy package permission check in telephony provider
+ // to pass.
+ assignWriteSmsPermissionToSystemUid(appOps, Process.PHONE_UID);
+
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal oldAppData=" + oldAppData);
+ }
+ if (oldAppData != null && oldAppData.mSmsAppChangedReceiverClass != null) {
+ // Notify the old sms app that it's no longer the default
+ final Intent oldAppIntent =
+ new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+ final ComponentName component = new ComponentName(oldAppData.mPackageName,
+ oldAppData.mSmsAppChangedReceiverClass);
+ oldAppIntent.setComponent(component);
+ oldAppIntent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, false);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal old=" + oldAppData.mPackageName);
+ }
+ context.sendBroadcast(oldAppIntent);
+ }
+ // Notify the new sms app that it's now the default (if the new sms app has a receiver
+ // to handle the changed default sms intent).
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal new applicationData=" +
+ applicationData);
+ }
+ if (applicationData.mSmsAppChangedReceiverClass != null) {
+ final Intent intent =
+ new Intent(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+ final ComponentName component = new ComponentName(applicationData.mPackageName,
+ applicationData.mSmsAppChangedReceiverClass);
+ intent.setComponent(component);
+ intent.putExtra(Telephony.Sms.Intents.EXTRA_IS_DEFAULT_SMS_APP, true);
+ if (DEBUG_MULTIUSER) {
+ Log.i(LOG_TAG, "setDefaultApplicationInternal new=" + packageName);
+ }
+ context.sendBroadcast(intent);
+ }
+ MetricsLogger.action(context, MetricsEvent.ACTION_DEFAULT_SMS_APP_CHANGED,
+ applicationData.mPackageName);
+ }
+ }
+
+ /**
+ * Assign WRITE_SMS AppOps permission to some special system apps.
+ *
+ * @param context The context
+ * @param packageManager The package manager instance
+ * @param appOps The AppOps manager instance
+ * @param packageName The package name of the system app
+ */
+ private static void assignWriteSmsPermissionToSystemApp(Context context,
+ PackageManager packageManager, AppOpsManager appOps, String packageName) {
+ // First check package signature matches the caller's package signature.
+ // Since this class is only used internally by the system, this check makes sure
+ // the package signature matches system signature.
+ final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
+ if (result != PackageManager.SIGNATURE_MATCH) {
+ Rlog.e(LOG_TAG, packageName + " does not have system signature");
+ return;
+ }
+ try {
+ PackageInfo info = packageManager.getPackageInfo(packageName, 0);
+ int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+ packageName);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)");
+ appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+ packageName, AppOpsManager.MODE_ALLOWED);
+ }
+ } catch (NameNotFoundException e) {
+ // No whitelisted system app on this device
+ Rlog.e(LOG_TAG, "Package not found: " + packageName);
+ }
+
+ }
+
+ private static void assignWriteSmsPermissionToSystemUid(AppOpsManager appOps, int uid) {
+ appOps.setUidMode(AppOpsManager.OP_WRITE_SMS, uid, AppOpsManager.MODE_ALLOWED);
+ }
+
+ /**
+ * Tracks package changes and ensures that the default SMS app is always configured to be the
+ * preferred activity for SENDTO sms/mms intents.
+ */
+ private static final class SmsPackageMonitor extends PackageMonitor {
+ final Context mContext;
+
+ public SmsPackageMonitor(Context context) {
+ super();
+ mContext = context;
+ }
+
+ @Override
+ public void onPackageDisappeared(String packageName, int reason) {
+ onPackageChanged();
+ }
+
+ @Override
+ public void onPackageAppeared(String packageName, int reason) {
+ onPackageChanged();
+ }
+
+ @Override
+ public void onPackageModified(String packageName) {
+ onPackageChanged();
+ }
+
+ private void onPackageChanged() {
+ PackageManager packageManager = mContext.getPackageManager();
+ Context userContext = mContext;
+ final int userId = getSendingUserId();
+ if (userId != UserHandle.USER_SYSTEM) {
+ try {
+ userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
+ new UserHandle(userId));
+ } catch (NameNotFoundException nnfe) {
+ if (DEBUG_MULTIUSER) {
+ Log.w(LOG_TAG, "Unable to create package context for user " + userId);
+ }
+ }
+ }
+ // Ensure this component is still configured as the preferred activity
+ ComponentName componentName = getDefaultSendToApplication(userContext, true);
+ if (componentName != null) {
+ configurePreferredActivity(packageManager, componentName, userId);
+ }
+ }
+ }
+
+ public static void initSmsPackageMonitor(Context context) {
+ sSmsPackageMonitor = new SmsPackageMonitor(context);
+ sSmsPackageMonitor.register(context, context.getMainLooper(), UserHandle.ALL, false);
+ }
+
+ private static void configurePreferredActivity(PackageManager packageManager,
+ ComponentName componentName, int userId) {
+ // Add the four activity preferences we want to direct to this app.
+ replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS);
+ replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO);
+ replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS);
+ replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO);
+ }
+
+ /**
+ * Updates the ACTION_SENDTO activity to the specified component for the specified scheme.
+ */
+ private static void replacePreferredActivity(PackageManager packageManager,
+ ComponentName componentName, int userId, String scheme) {
+ // Build the set of existing activities that handle this scheme
+ Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null));
+ List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivitiesAsUser(
+ intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER,
+ userId);
+
+ // Build the set of ComponentNames for these activities
+ final int n = resolveInfoList.size();
+ ComponentName[] set = new ComponentName[n];
+ for (int i = 0; i < n; i++) {
+ ResolveInfo info = resolveInfoList.get(i);
+ set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
+ }
+
+ // Update the preferred SENDTO activity for the specified scheme
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_SENDTO);
+ intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ intentFilter.addDataScheme(scheme);
+ packageManager.replacePreferredActivityAsUser(intentFilter,
+ IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
+ set, componentName, userId);
+ }
+
+ /**
+ * Returns SmsApplicationData for this package if this package is capable of being set as the
+ * default SMS application.
+ */
+ public static SmsApplicationData getSmsApplicationData(String packageName, Context context) {
+ Collection<SmsApplicationData> applications = getApplicationCollection(context);
+ return getApplicationForPackage(applications, packageName);
+ }
+
+ /**
+ * Gets the default SMS application
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to deliver SMS messages to
+ */
+ public static ComponentName getDefaultSmsApplication(Context context, boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mSmsReceiverClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default MMS application
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to deliver MMS messages to
+ */
+ public static ComponentName getDefaultMmsApplication(Context context, boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mMmsReceiverClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default Respond Via Message application
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to direct Respond Via Message intent to
+ */
+ public static ComponentName getDefaultRespondViaMessageApplication(Context context,
+ boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mRespondViaMessageClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default Send To (smsto) application.
+ * <p>
+ * Caller must pass in the correct user context if calling from a singleton service.
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to direct SEND_TO (smsto) intent to
+ */
+ public static ComponentName getDefaultSendToApplication(Context context,
+ boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mSendToClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default application that handles external changes to the SmsProvider and
+ * MmsProvider.
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to deliver change intents to
+ */
+ public static ComponentName getDefaultExternalTelephonyProviderChangedApplication(
+ Context context, boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null
+ && smsApplicationData.mProviderChangedReceiverClass != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mProviderChangedReceiverClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Gets the default application that handles sim full event.
+ * @param context context from the calling app
+ * @param updateIfNeeded update the default app if there is no valid default app configured.
+ * @return component name of the app and class to deliver change intents to
+ */
+ public static ComponentName getDefaultSimFullApplication(
+ Context context, boolean updateIfNeeded) {
+ int userId = getIncomingUserId(context);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ComponentName component = null;
+ SmsApplicationData smsApplicationData = getApplication(context, updateIfNeeded,
+ userId);
+ if (smsApplicationData != null
+ && smsApplicationData.mSimFullReceiverClass != null) {
+ component = new ComponentName(smsApplicationData.mPackageName,
+ smsApplicationData.mSimFullReceiverClass);
+ }
+ return component;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Returns whether need to write the SMS message to SMS database for this package.
+ * <p>
+ * Caller must pass in the correct user context if calling from a singleton service.
+ */
+ public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
+ if (SmsManager.getDefault().getAutoPersisting()) {
+ return true;
+ }
+ return !isDefaultSmsApplication(context, packageName);
+ }
+
+ /**
+ * Check if a package is default sms app (or equivalent, like bluetooth)
+ *
+ * @param context context from the calling app
+ * @param packageName the name of the package to be checked
+ * @return true if the package is default sms app or bluetooth
+ */
+ public static boolean isDefaultSmsApplication(Context context, String packageName) {
+ if (packageName == null) {
+ return false;
+ }
+ final String defaultSmsPackage = getDefaultSmsApplicationPackageName(context);
+ if ((defaultSmsPackage != null && defaultSmsPackage.equals(packageName))
+ || BLUETOOTH_PACKAGE_NAME.equals(packageName)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static String getDefaultSmsApplicationPackageName(Context context) {
+ final ComponentName component = getDefaultSmsApplication(context, false);
+ if (component != null) {
+ return component.getPackageName();
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
new file mode 100644
index 0000000..c912924
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbCmasInfo.java
@@ -0,0 +1,310 @@
+/*
+ * 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>() {
+ @Override
+ public SmsCbCmasInfo createFromParcel(Parcel in) {
+ return new SmsCbCmasInfo(in);
+ }
+
+ @Override
+ public SmsCbCmasInfo[] newArray(int size) {
+ return new SmsCbCmasInfo[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java b/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
new file mode 100644
index 0000000..14e02de
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbEtwsInfo.java
@@ -0,0 +1,222 @@
+/*
+ * 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.uicc.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;
+
+ /** Whether ETWS primary message or not/ */
+ private final boolean mPrimary;
+
+ /**
+ * 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,
+ boolean primary, byte[] warningSecurityInformation) {
+ mWarningType = warningType;
+ mEmergencyUserAlert = emergencyUserAlert;
+ mActivatePopup = activatePopup;
+ mPrimary = primary;
+ mWarningSecurityInformation = warningSecurityInformation;
+ }
+
+ /** Create a new SmsCbEtwsInfo object from a Parcel. */
+ SmsCbEtwsInfo(Parcel in) {
+ mWarningType = in.readInt();
+ mEmergencyUserAlert = (in.readInt() != 0);
+ mActivatePopup = (in.readInt() != 0);
+ mPrimary = (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.writeInt(mPrimary ? 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 ETWS format flag.
+ * @return true if the message is primary message, otherwise secondary message
+ */
+ public boolean isPrimary() {
+ return mPrimary;
+ }
+
+ /**
+ * 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) - 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>() {
+ @Override
+ public SmsCbEtwsInfo createFromParcel(Parcel in) {
+ return new SmsCbEtwsInfo(in);
+ }
+
+ @Override
+ public SmsCbEtwsInfo[] newArray(int size) {
+ return new SmsCbEtwsInfo[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsCbLocation.java b/telephony/java/com/android/internal/telephony/SmsCbLocation.java
new file mode 100644
index 0000000..6eb72a86
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbLocation.java
@@ -0,0 +1,200 @@
+/*
+ * 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;
+
+/**
+ * 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/com/android/internal/telephony/SmsCbMessage.java b/telephony/java/com/android/internal/telephony/SmsCbMessage.java
new file mode 100644
index 0000000..046bf8c
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsCbMessage.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * 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
+ */
+public class SmsCbMessage implements Parcelable {
+
+ 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 (GSM/UMTS and all CDMA broadcasts). */
+ public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
+
+ /** Location / service area wide geographical scope (GSM/UMTS only). */
+ public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
+
+ /** 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;
+
+ /**
+ * 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.
+ */
+ private final int mSerialNumber;
+
+ /**
+ * 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 final SmsCbLocation mLocation;
+
+ /**
+ * 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 final int mServiceCategory;
+
+ /** Message language, as a two-character string, e.g. "en". */
+ private final String mLanguage;
+
+ /** Message body, as a String. */
+ private final String mBody;
+
+ /** Message priority (including emergency priority). */
+ private final int mPriority;
+
+ /** ETWS warning notification information (ETWS warnings only). */
+ private final SmsCbEtwsInfo mEtwsWarningInfo;
+
+ /** CMAS warning notification information (CMAS warnings only). */
+ private final SmsCbCmasInfo mCmasWarningInfo;
+
+ /**
+ * 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;
+ }
+
+ /** 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;
+
+ case 'C':
+ // unparcel CMAS warning information
+ mEtwsWarningInfo = null;
+ mCmasWarningInfo = new SmsCbCmasInfo(in);
+ break;
+
+ default:
+ mEtwsWarningInfo = null;
+ mCmasWarningInfo = null;
+ }
+ }
+
+ /**
+ * 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(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];
+ }
+ };
+
+ /**
+ * Return the geographical scope of this message (GSM/UMTS only).
+ *
+ * @return Geographical scope
+ */
+ public int getGeographicalScope() {
+ return mGeographicalScope;
+ }
+
+ /**
+ * 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 the 16-bit CDMA message identifier or GSM/UMTS serial number
+ */
+ public int getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ /**
+ * 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 the geographical location code for duplicate message detection
+ */
+ public SmsCbLocation getLocation() {
+ return mLocation;
+ }
+
+ /**
+ * 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 the radio technology specific service category
+ */
+ public int getServiceCategory() {
+ return mServiceCategory;
+ }
+
+ /**
+ * Get the ISO-639-1 language code for this message, or null if unspecified
+ *
+ * @return Language code
+ */
+ public String getLanguageCode() {
+ return mLanguage;
+ }
+
+ /**
+ * Get the body of this message, or null if no body available
+ *
+ * @return Body, or null
+ */
+ public String getMessageBody() {
+ return mBody;
+ }
+
+ /**
+ * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
+ * @return an integer representing 3GPP or 3GPP2 message format
+ */
+ public int getMessageFormat() {
+ return mMessageFormat;
+ }
+
+ /**
+ * 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 int getMessagePriority() {
+ return mPriority;
+ }
+
+ /**
+ * 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 SmsCbEtwsInfo getEtwsWarningInfo() {
+ return mEtwsWarningInfo;
+ }
+
+ /**
+ * 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 SmsCbCmasInfo getCmasWarningInfo() {
+ return mCmasWarningInfo;
+ }
+
+ /**
+ * Return whether this message is an emergency (PWS) message type.
+ * @return true if the message is a public warning notification; false otherwise
+ */
+ public boolean isEmergencyMessage() {
+ return mPriority == MESSAGE_PRIORITY_EMERGENCY;
+ }
+
+ /**
+ * Return whether this message is an ETWS warning alert.
+ * @return true if the message is an ETWS warning notification; false otherwise
+ */
+ public boolean isEtwsMessage() {
+ return mEtwsWarningInfo != null;
+ }
+
+ /**
+ * Return whether this message is a CMAS warning alert.
+ * @return true if the message is a CMAS warning notification; false otherwise
+ */
+ public boolean isCmasMessage() {
+ return mCmasWarningInfo != null;
+ }
+
+ @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()) : "") + '}';
+ }
+
+ /**
+ * 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/com/android/internal/telephony/SmsHeader.java b/telephony/java/com/android/internal/telephony/SmsHeader.java
new file mode 100644
index 0000000..b519b70
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsHeader.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2006 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;
+
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import java.util.ArrayList;
+
+/**
+ * SMS user data header, as specified in TS 23.040 9.2.3.24.
+ */
+public class SmsHeader {
+
+ // TODO(cleanup): this data structure is generally referred to as
+ // the 'user data header' or UDH, and so the class name should
+ // change to reflect this...
+
+ /** SMS user data header information element identifiers.
+ * (see TS 23.040 9.2.3.24)
+ */
+ public static final int ELT_ID_CONCATENATED_8_BIT_REFERENCE = 0x00;
+ public static final int ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION = 0x01;
+ public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT = 0x04;
+ public static final int ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT = 0x05;
+ public static final int ELT_ID_SMSC_CONTROL_PARAMS = 0x06;
+ public static final int ELT_ID_UDH_SOURCE_INDICATION = 0x07;
+ public static final int ELT_ID_CONCATENATED_16_BIT_REFERENCE = 0x08;
+ public static final int ELT_ID_WIRELESS_CTRL_MSG_PROTOCOL = 0x09;
+ public static final int ELT_ID_TEXT_FORMATTING = 0x0A;
+ public static final int ELT_ID_PREDEFINED_SOUND = 0x0B;
+ public static final int ELT_ID_USER_DEFINED_SOUND = 0x0C;
+ public static final int ELT_ID_PREDEFINED_ANIMATION = 0x0D;
+ public static final int ELT_ID_LARGE_ANIMATION = 0x0E;
+ public static final int ELT_ID_SMALL_ANIMATION = 0x0F;
+ public static final int ELT_ID_LARGE_PICTURE = 0x10;
+ public static final int ELT_ID_SMALL_PICTURE = 0x11;
+ public static final int ELT_ID_VARIABLE_PICTURE = 0x12;
+ public static final int ELT_ID_USER_PROMPT_INDICATOR = 0x13;
+ public static final int ELT_ID_EXTENDED_OBJECT = 0x14;
+ public static final int ELT_ID_REUSED_EXTENDED_OBJECT = 0x15;
+ public static final int ELT_ID_COMPRESSION_CONTROL = 0x16;
+ public static final int ELT_ID_OBJECT_DISTR_INDICATOR = 0x17;
+ public static final int ELT_ID_STANDARD_WVG_OBJECT = 0x18;
+ public static final int ELT_ID_CHARACTER_SIZE_WVG_OBJECT = 0x19;
+ public static final int ELT_ID_EXTENDED_OBJECT_DATA_REQUEST_CMD = 0x1A;
+ public static final int ELT_ID_RFC_822_EMAIL_HEADER = 0x20;
+ public static final int ELT_ID_HYPERLINK_FORMAT_ELEMENT = 0x21;
+ public static final int ELT_ID_REPLY_ADDRESS_ELEMENT = 0x22;
+ public static final int ELT_ID_ENHANCED_VOICE_MAIL_INFORMATION = 0x23;
+ public static final int ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT = 0x24;
+ public static final int ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT = 0x25;
+
+ public static final int PORT_WAP_PUSH = 2948;
+ public static final int PORT_WAP_WSP = 9200;
+
+ public static class PortAddrs {
+ public int destPort;
+ public int origPort;
+ public boolean areEightBits;
+ }
+
+ public static class ConcatRef {
+ public int refNumber;
+ public int seqNumber;
+ public int msgCount;
+ public boolean isEightBits;
+ }
+
+ public static class SpecialSmsMsg {
+ public int msgIndType;
+ public int msgCount;
+ }
+
+ /**
+ * A header element that is not explicitly parsed, meaning not
+ * PortAddrs or ConcatRef or SpecialSmsMsg.
+ */
+ public static class MiscElt {
+ public int id;
+ public byte[] data;
+ }
+
+ public PortAddrs portAddrs;
+ public ConcatRef concatRef;
+ public ArrayList<SpecialSmsMsg> specialSmsMsgList = new ArrayList<SpecialSmsMsg>();
+ public ArrayList<MiscElt> miscEltList = new ArrayList<MiscElt>();
+
+ /** 7 bit national language locking shift table, or 0 for GSM default 7 bit alphabet. */
+ public int languageTable;
+
+ /** 7 bit national language single shift table, or 0 for GSM default 7 bit extension table. */
+ public int languageShiftTable;
+
+ public SmsHeader() {}
+
+ /**
+ * Create structured SmsHeader object from serialized byte array representation.
+ * (see TS 23.040 9.2.3.24)
+ * @param data is user data header bytes
+ * @return SmsHeader object
+ */
+ public static SmsHeader fromByteArray(byte[] data) {
+ ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+ SmsHeader smsHeader = new SmsHeader();
+ while (inStream.available() > 0) {
+ /**
+ * NOTE: as defined in the spec, ConcatRef and PortAddr
+ * fields should not reoccur, but if they do the last
+ * occurrence is to be used. Also, for ConcatRef
+ * elements, if the count is zero, sequence is zero, or
+ * sequence is larger than count, the entire element is to
+ * be ignored.
+ */
+ int id = inStream.read();
+ int length = inStream.read();
+ ConcatRef concatRef;
+ PortAddrs portAddrs;
+ switch (id) {
+ case ELT_ID_CONCATENATED_8_BIT_REFERENCE:
+ concatRef = new ConcatRef();
+ concatRef.refNumber = inStream.read();
+ concatRef.msgCount = inStream.read();
+ concatRef.seqNumber = inStream.read();
+ concatRef.isEightBits = true;
+ if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
+ concatRef.seqNumber <= concatRef.msgCount) {
+ smsHeader.concatRef = concatRef;
+ }
+ break;
+ case ELT_ID_CONCATENATED_16_BIT_REFERENCE:
+ concatRef = new ConcatRef();
+ concatRef.refNumber = (inStream.read() << 8) | inStream.read();
+ concatRef.msgCount = inStream.read();
+ concatRef.seqNumber = inStream.read();
+ concatRef.isEightBits = false;
+ if (concatRef.msgCount != 0 && concatRef.seqNumber != 0 &&
+ concatRef.seqNumber <= concatRef.msgCount) {
+ smsHeader.concatRef = concatRef;
+ }
+ break;
+ case ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT:
+ portAddrs = new PortAddrs();
+ portAddrs.destPort = inStream.read();
+ portAddrs.origPort = inStream.read();
+ portAddrs.areEightBits = true;
+ smsHeader.portAddrs = portAddrs;
+ break;
+ case ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT:
+ portAddrs = new PortAddrs();
+ portAddrs.destPort = (inStream.read() << 8) | inStream.read();
+ portAddrs.origPort = (inStream.read() << 8) | inStream.read();
+ portAddrs.areEightBits = false;
+ smsHeader.portAddrs = portAddrs;
+ break;
+ case ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT:
+ smsHeader.languageShiftTable = inStream.read();
+ break;
+ case ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT:
+ smsHeader.languageTable = inStream.read();
+ break;
+ case ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION:
+ SpecialSmsMsg specialSmsMsg = new SpecialSmsMsg();
+ specialSmsMsg.msgIndType = inStream.read();
+ specialSmsMsg.msgCount = inStream.read();
+ smsHeader.specialSmsMsgList.add(specialSmsMsg);
+ break;
+ default:
+ MiscElt miscElt = new MiscElt();
+ miscElt.id = id;
+ miscElt.data = new byte[length];
+ inStream.read(miscElt.data, 0, length);
+ smsHeader.miscEltList.add(miscElt);
+ }
+ }
+ return smsHeader;
+ }
+
+ /**
+ * Create serialized byte array representation from structured SmsHeader object.
+ * (see TS 23.040 9.2.3.24)
+ * @return Byte array representing the SmsHeader
+ */
+ public static byte[] toByteArray(SmsHeader smsHeader) {
+ if ((smsHeader.portAddrs == null) &&
+ (smsHeader.concatRef == null) &&
+ (smsHeader.specialSmsMsgList.isEmpty()) &&
+ (smsHeader.miscEltList.isEmpty()) &&
+ (smsHeader.languageShiftTable == 0) &&
+ (smsHeader.languageTable == 0)) {
+ return null;
+ }
+
+ ByteArrayOutputStream outStream =
+ new ByteArrayOutputStream(SmsConstants.MAX_USER_DATA_BYTES);
+ ConcatRef concatRef = smsHeader.concatRef;
+ if (concatRef != null) {
+ if (concatRef.isEightBits) {
+ outStream.write(ELT_ID_CONCATENATED_8_BIT_REFERENCE);
+ outStream.write(3);
+ outStream.write(concatRef.refNumber);
+ } else {
+ outStream.write(ELT_ID_CONCATENATED_16_BIT_REFERENCE);
+ outStream.write(4);
+ outStream.write(concatRef.refNumber >>> 8);
+ outStream.write(concatRef.refNumber & 0x00FF);
+ }
+ outStream.write(concatRef.msgCount);
+ outStream.write(concatRef.seqNumber);
+ }
+ PortAddrs portAddrs = smsHeader.portAddrs;
+ if (portAddrs != null) {
+ if (portAddrs.areEightBits) {
+ outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_8_BIT);
+ outStream.write(2);
+ outStream.write(portAddrs.destPort);
+ outStream.write(portAddrs.origPort);
+ } else {
+ outStream.write(ELT_ID_APPLICATION_PORT_ADDRESSING_16_BIT);
+ outStream.write(4);
+ outStream.write(portAddrs.destPort >>> 8);
+ outStream.write(portAddrs.destPort & 0x00FF);
+ outStream.write(portAddrs.origPort >>> 8);
+ outStream.write(portAddrs.origPort & 0x00FF);
+ }
+ }
+ if (smsHeader.languageShiftTable != 0) {
+ outStream.write(ELT_ID_NATIONAL_LANGUAGE_SINGLE_SHIFT);
+ outStream.write(1);
+ outStream.write(smsHeader.languageShiftTable);
+ }
+ if (smsHeader.languageTable != 0) {
+ outStream.write(ELT_ID_NATIONAL_LANGUAGE_LOCKING_SHIFT);
+ outStream.write(1);
+ outStream.write(smsHeader.languageTable);
+ }
+ for (SpecialSmsMsg specialSmsMsg : smsHeader.specialSmsMsgList) {
+ outStream.write(ELT_ID_SPECIAL_SMS_MESSAGE_INDICATION);
+ outStream.write(2);
+ outStream.write(specialSmsMsg.msgIndType & 0xFF);
+ outStream.write(specialSmsMsg.msgCount & 0xFF);
+ }
+ for (MiscElt miscElt : smsHeader.miscEltList) {
+ outStream.write(miscElt.id);
+ outStream.write(miscElt.data.length);
+ outStream.write(miscElt.data, 0, miscElt.data.length);
+ }
+ return outStream.toByteArray();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("UserDataHeader ");
+ builder.append("{ ConcatRef ");
+ if (concatRef == null) {
+ builder.append("unset");
+ } else {
+ builder.append("{ refNumber=" + concatRef.refNumber);
+ builder.append(", msgCount=" + concatRef.msgCount);
+ builder.append(", seqNumber=" + concatRef.seqNumber);
+ builder.append(", isEightBits=" + concatRef.isEightBits);
+ builder.append(" }");
+ }
+ builder.append(", PortAddrs ");
+ if (portAddrs == null) {
+ builder.append("unset");
+ } else {
+ builder.append("{ destPort=" + portAddrs.destPort);
+ builder.append(", origPort=" + portAddrs.origPort);
+ builder.append(", areEightBits=" + portAddrs.areEightBits);
+ builder.append(" }");
+ }
+ if (languageShiftTable != 0) {
+ builder.append(", languageShiftTable=" + languageShiftTable);
+ }
+ if (languageTable != 0) {
+ builder.append(", languageTable=" + languageTable);
+ }
+ for (SpecialSmsMsg specialSmsMsg : specialSmsMsgList) {
+ builder.append(", SpecialSmsMsg ");
+ builder.append("{ msgIndType=" + specialSmsMsg.msgIndType);
+ builder.append(", msgCount=" + specialSmsMsg.msgCount);
+ builder.append(" }");
+ }
+ for (MiscElt miscElt : miscEltList) {
+ builder.append(", MiscElt ");
+ builder.append("{ id=" + miscElt.id);
+ builder.append(", length=" + miscElt.data.length);
+ builder.append(", data=" + HexDump.toHexString(miscElt.data));
+ builder.append(" }");
+ }
+ builder.append(" }");
+ return builder.toString();
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
new file mode 100644
index 0000000..e5821dc
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2008 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;
+
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import java.text.BreakIterator;
+import java.util.Arrays;
+
+import android.provider.Telephony;
+import android.telephony.SmsMessage;
+import android.text.Emoji;
+
+/**
+ * Base class declaring the specific methods and members for SmsMessage.
+ * {@hide}
+ */
+public abstract class SmsMessageBase {
+ /** {@hide} The address of the SMSC. May be null */
+ protected String mScAddress;
+
+ /** {@hide} The address of the sender */
+ protected SmsAddress mOriginatingAddress;
+
+ /** {@hide} The message body as a string. May be null if the message isn't text */
+ protected String mMessageBody;
+
+ /** {@hide} */
+ protected String mPseudoSubject;
+
+ /** {@hide} Non-null if this is an email gateway message */
+ protected String mEmailFrom;
+
+ /** {@hide} Non-null if this is an email gateway message */
+ protected String mEmailBody;
+
+ /** {@hide} */
+ protected boolean mIsEmail;
+
+ /** {@hide} Time when SC (service centre) received the message */
+ protected long mScTimeMillis;
+
+ /** {@hide} The raw PDU of the message */
+ protected byte[] mPdu;
+
+ /** {@hide} The raw bytes for the user data section of the message */
+ protected byte[] mUserData;
+
+ /** {@hide} */
+ protected SmsHeader mUserDataHeader;
+
+ // "Message Waiting Indication Group"
+ // 23.038 Section 4
+ /** {@hide} */
+ protected boolean mIsMwi;
+
+ /** {@hide} */
+ protected boolean mMwiSense;
+
+ /** {@hide} */
+ protected boolean mMwiDontStore;
+
+ /**
+ * Indicates status for messages stored on the ICC.
+ */
+ protected int mStatusOnIcc = -1;
+
+ /**
+ * Record index of message in the EF.
+ */
+ protected int mIndexOnIcc = -1;
+
+ /** TP-Message-Reference - Message Reference of sent message. @hide */
+ public int mMessageRef;
+
+ // TODO(): This class is duplicated in SmsMessage.java. Refactor accordingly.
+ public static abstract class SubmitPduBase {
+ public byte[] encodedScAddress; // Null if not applicable.
+ public byte[] encodedMessage;
+
+ @Override
+ public String toString() {
+ return "SubmitPdu: encodedScAddress = "
+ + Arrays.toString(encodedScAddress)
+ + ", encodedMessage = "
+ + Arrays.toString(encodedMessage);
+ }
+ }
+
+ /**
+ * Returns the address of the SMS service center that relayed this message
+ * or null if there is none.
+ */
+ public String getServiceCenterAddress() {
+ return mScAddress;
+ }
+
+ /**
+ * Returns the originating address (sender) of this SMS message in String
+ * form or null if unavailable
+ */
+ public String getOriginatingAddress() {
+ if (mOriginatingAddress == null) {
+ return null;
+ }
+
+ return mOriginatingAddress.getAddressString();
+ }
+
+ /**
+ * Returns the originating address, or email from address if this message
+ * was from an email gateway. Returns null if originating address
+ * unavailable.
+ */
+ public String getDisplayOriginatingAddress() {
+ if (mIsEmail) {
+ return mEmailFrom;
+ } else {
+ return getOriginatingAddress();
+ }
+ }
+
+ /**
+ * Returns the message body as a String, if it exists and is text based.
+ * @return message body is there is one, otherwise null
+ */
+ public String getMessageBody() {
+ return mMessageBody;
+ }
+
+ /**
+ * Returns the class of this message.
+ */
+ public abstract SmsConstants.MessageClass getMessageClass();
+
+ /**
+ * Returns the message body, or email message body if this message was from
+ * an email gateway. Returns null if message body unavailable.
+ */
+ public String getDisplayMessageBody() {
+ if (mIsEmail) {
+ return mEmailBody;
+ } else {
+ return getMessageBody();
+ }
+ }
+
+ /**
+ * Unofficial convention of a subject line enclosed in parens empty string
+ * if not present
+ */
+ public String getPseudoSubject() {
+ return mPseudoSubject == null ? "" : mPseudoSubject;
+ }
+
+ /**
+ * Returns the service centre timestamp in currentTimeMillis() format
+ */
+ public long getTimestampMillis() {
+ return mScTimeMillis;
+ }
+
+ /**
+ * Returns true if message is an email.
+ *
+ * @return true if this message came through an email gateway and email
+ * sender / subject / parsed body are available
+ */
+ public boolean isEmail() {
+ return mIsEmail;
+ }
+
+ /**
+ * @return if isEmail() is true, body of the email sent through the gateway.
+ * null otherwise
+ */
+ public String getEmailBody() {
+ return mEmailBody;
+ }
+
+ /**
+ * @return if isEmail() is true, email from address of email sent through
+ * the gateway. null otherwise
+ */
+ public String getEmailFrom() {
+ return mEmailFrom;
+ }
+
+ /**
+ * Get protocol identifier.
+ */
+ public abstract int getProtocolIdentifier();
+
+ /**
+ * See TS 23.040 9.2.3.9 returns true if this is a "replace short message"
+ * SMS
+ */
+ public abstract boolean isReplace();
+
+ /**
+ * Returns true for CPHS MWI toggle message.
+ *
+ * @return true if this is a CPHS MWI toggle message See CPHS 4.2 section
+ * B.4.2
+ */
+ public abstract boolean isCphsMwiMessage();
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) clear message
+ */
+ public abstract boolean isMWIClearMessage();
+
+ /**
+ * returns true if this message is a CPHS voicemail / message waiting
+ * indicator (MWI) set message
+ */
+ public abstract boolean isMWISetMessage();
+
+ /**
+ * returns true if this message is a "Message Waiting Indication Group:
+ * Discard Message" notification and should not be stored.
+ */
+ public abstract boolean isMwiDontStore();
+
+ /**
+ * returns the user data section minus the user data header if one was
+ * present.
+ */
+ public byte[] getUserData() {
+ return mUserData;
+ }
+
+ /**
+ * Returns an object representing the user data header
+ *
+ * {@hide}
+ */
+ public SmsHeader getUserDataHeader() {
+ return mUserDataHeader;
+ }
+
+ /**
+ * TODO(cleanup): The term PDU is used in a seemingly non-unique
+ * manner -- for example, what is the difference between this byte
+ * array and the contents of SubmitPdu objects. Maybe a more
+ * illustrative term would be appropriate.
+ */
+
+ /**
+ * Returns the raw PDU for the message.
+ */
+ public byte[] getPdu() {
+ return mPdu;
+ }
+
+ /**
+ * For an SMS-STATUS-REPORT message, this returns the status field from
+ * the status report. This field indicates the status of a previously
+ * submitted SMS, if requested. See TS 23.040, 9.2.3.15 TP-Status for a
+ * description of values.
+ *
+ * @return 0 indicates the previously sent message was received.
+ * See TS 23.040, 9.9.2.3.15 for a description of other possible
+ * values.
+ */
+ public abstract int getStatus();
+
+ /**
+ * Return true iff the message is a SMS-STATUS-REPORT message.
+ */
+ public abstract boolean isStatusReportMessage();
+
+ /**
+ * Returns true iff the <code>TP-Reply-Path</code> bit is set in
+ * this message.
+ */
+ public abstract boolean isReplyPathPresent();
+
+ /**
+ * Returns the status of the message on the ICC (read, unread, sent, unsent).
+ *
+ * @return the status of the message on the ICC. These are:
+ * SmsManager.STATUS_ON_ICC_FREE
+ * SmsManager.STATUS_ON_ICC_READ
+ * SmsManager.STATUS_ON_ICC_UNREAD
+ * SmsManager.STATUS_ON_ICC_SEND
+ * SmsManager.STATUS_ON_ICC_UNSENT
+ */
+ public int getStatusOnIcc() {
+ return mStatusOnIcc;
+ }
+
+ /**
+ * Returns the record index of the message on the ICC (1-based index).
+ * @return the record index of the message on the ICC, or -1 if this
+ * SmsMessage was not created from a ICC SMS EF record.
+ */
+ public int getIndexOnIcc() {
+ return mIndexOnIcc;
+ }
+
+ protected void parseMessageBody() {
+ // originatingAddress could be null if this message is from a status
+ // report.
+ if (mOriginatingAddress != null && mOriginatingAddress.couldBeEmailGateway()) {
+ extractEmailAddressFromMessageBody();
+ }
+ }
+
+ /**
+ * Try to parse this message as an email gateway message
+ * There are two ways specified in TS 23.040 Section 3.8 :
+ * - SMS message "may have its TP-PID set for Internet electronic mail - MT
+ * SMS format: [<from-address><space>]<message> - "Depending on the
+ * nature of the gateway, the destination/origination address is either
+ * derived from the content of the SMS TP-OA or TP-DA field, or the
+ * TP-OA/TP-DA field contains a generic gateway address and the to/from
+ * address is added at the beginning as shown above." (which is supported here)
+ * - Multiple addresses separated by commas, no spaces, Subject field delimited
+ * by '()' or '##' and '#' Section 9.2.3.24.11 (which are NOT supported here)
+ */
+ protected void extractEmailAddressFromMessageBody() {
+
+ /* Some carriers may use " /" delimiter as below
+ *
+ * 1. [x@y][ ]/[subject][ ]/[body]
+ * -or-
+ * 2. [x@y][ ]/[body]
+ */
+ String[] parts = mMessageBody.split("( /)|( )", 2);
+ if (parts.length < 2) return;
+ mEmailFrom = parts[0];
+ mEmailBody = parts[1];
+ mIsEmail = Telephony.Mms.isEmailAddress(mEmailFrom);
+ }
+
+ /**
+ * Find the next position to start a new fragment of a multipart SMS.
+ *
+ * @param currentPosition current start position of the fragment
+ * @param byteLimit maximum number of bytes in the fragment
+ * @param msgBody text of the SMS in UTF-16 encoding
+ * @return the position to start the next fragment
+ */
+ public static int findNextUnicodePosition(
+ int currentPosition, int byteLimit, CharSequence msgBody) {
+ int nextPos = Math.min(currentPosition + byteLimit / 2, msgBody.length());
+ // Check whether the fragment ends in a character boundary. Some characters take 4-bytes
+ // in UTF-16 encoding. Many carriers cannot handle
+ // a fragment correctly if it does not end at a character boundary.
+ if (nextPos < msgBody.length()) {
+ BreakIterator breakIterator = BreakIterator.getCharacterInstance();
+ breakIterator.setText(msgBody.toString());
+ if (!breakIterator.isBoundary(nextPos)) {
+ int breakPos = breakIterator.preceding(nextPos);
+ while (breakPos + 4 <= nextPos
+ && Emoji.isRegionalIndicatorSymbol(
+ Character.codePointAt(msgBody, breakPos))
+ && Emoji.isRegionalIndicatorSymbol(
+ Character.codePointAt(msgBody, breakPos + 2))) {
+ // skip forward over flags (pairs of Regional Indicator Symbol)
+ breakPos += 4;
+ }
+ if (breakPos > currentPosition) {
+ nextPos = breakPos;
+ } else if (Character.isHighSurrogate(msgBody.charAt(nextPos - 1))) {
+ // no character boundary in this fragment, try to at least land on a code point
+ nextPos -= 1;
+ }
+ }
+ }
+ return nextPos;
+ }
+
+ /**
+ * Calculate the TextEncodingDetails of a message encoded in Unicode.
+ */
+ public static TextEncodingDetails calcUnicodeEncodingDetails(CharSequence msgBody) {
+ TextEncodingDetails ted = new TextEncodingDetails();
+ int octets = msgBody.length() * 2;
+ ted.codeUnitSize = SmsConstants.ENCODING_16BIT;
+ ted.codeUnitCount = msgBody.length();
+ if (octets > SmsConstants.MAX_USER_DATA_BYTES) {
+ // If EMS is not supported, break down EMS into single segment SMS
+ // and add page info " x/y".
+ // In the case of UCS2 encoding type, we need 8 bytes for this
+ // but we only have 6 bytes from UDH, so truncate the limit for
+ // each segment by 2 bytes (1 char).
+ int maxUserDataBytesWithHeader = SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+ if (!SmsMessage.hasEmsSupport()) {
+ // make sure total number of segments is less than 10
+ if (octets <= 9 * (maxUserDataBytesWithHeader - 2)) {
+ maxUserDataBytesWithHeader -= 2;
+ }
+ }
+
+ int pos = 0; // Index in code units.
+ int msgCount = 0;
+ while (pos < msgBody.length()) {
+ int nextPos = findNextUnicodePosition(pos, maxUserDataBytesWithHeader,
+ msgBody);
+ if (nextPos == msgBody.length()) {
+ ted.codeUnitsRemaining = pos + maxUserDataBytesWithHeader / 2 -
+ msgBody.length();
+ }
+ pos = nextPos;
+ msgCount++;
+ }
+ ted.msgCount = msgCount;
+ } else {
+ ted.msgCount = 1;
+ ted.codeUnitsRemaining = (SmsConstants.MAX_USER_DATA_BYTES - octets) / 2;
+ }
+
+ return ted;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/BearerData.java
new file mode 100644
index 0000000..a4cd56b
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/BearerData.java
@@ -0,0 +1,2025 @@
+/*
+ * Copyright (C) 2008 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.sms;
+
+import android.content.res.Resources;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.telephony.cdma.CdmaSmsCbProgramResults;
+import android.text.format.Time;
+import android.telephony.Rlog;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.BitwiseOutputStream;
+
+import java.util.ArrayList;
+import java.util.TimeZone;
+
+/**
+ * An object to encode and decode CDMA SMS bearer data.
+ */
+public final class BearerData {
+ private final static String LOG_TAG = "BearerData";
+
+ /**
+ * Bearer Data Subparameter Identifiers
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5-1)
+ * NOTE: Commented subparameter types are not implemented.
+ */
+ private final static byte SUBPARAM_MESSAGE_IDENTIFIER = 0x00;
+ private final static byte SUBPARAM_USER_DATA = 0x01;
+ private final static byte SUBPARAM_USER_RESPONSE_CODE = 0x02;
+ private final static byte SUBPARAM_MESSAGE_CENTER_TIME_STAMP = 0x03;
+ private final static byte SUBPARAM_VALIDITY_PERIOD_ABSOLUTE = 0x04;
+ private final static byte SUBPARAM_VALIDITY_PERIOD_RELATIVE = 0x05;
+ private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE = 0x06;
+ private final static byte SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE = 0x07;
+ private final static byte SUBPARAM_PRIORITY_INDICATOR = 0x08;
+ private final static byte SUBPARAM_PRIVACY_INDICATOR = 0x09;
+ private final static byte SUBPARAM_REPLY_OPTION = 0x0A;
+ private final static byte SUBPARAM_NUMBER_OF_MESSAGES = 0x0B;
+ private final static byte SUBPARAM_ALERT_ON_MESSAGE_DELIVERY = 0x0C;
+ private final static byte SUBPARAM_LANGUAGE_INDICATOR = 0x0D;
+ private final static byte SUBPARAM_CALLBACK_NUMBER = 0x0E;
+ private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE = 0x0F;
+ //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA = 0x10;
+ private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX = 0x11;
+ private final static byte SUBPARAM_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;
+ //private final static byte SUBPARAM_ENHANCED_VMN_ACK = 0x17;
+
+ // All other values after this are reserved.
+ private final static byte SUBPARAM_ID_LAST_DEFINED = 0x17;
+
+ /**
+ * Supported message types for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.1-1)
+ */
+ public static final int MESSAGE_TYPE_DELIVER = 0x01;
+ public static final int MESSAGE_TYPE_SUBMIT = 0x02;
+ public static final int MESSAGE_TYPE_CANCELLATION = 0x03;
+ public static final int MESSAGE_TYPE_DELIVERY_ACK = 0x04;
+ public static final int MESSAGE_TYPE_USER_ACK = 0x05;
+ public static final int MESSAGE_TYPE_READ_ACK = 0x06;
+ public static final int MESSAGE_TYPE_DELIVER_REPORT = 0x07;
+ public static final int MESSAGE_TYPE_SUBMIT_REPORT = 0x08;
+
+ public int messageType;
+
+ /**
+ * 16-bit value indicating the message ID, which increments modulo 65536.
+ * (Special rules apply for WAP-messages.)
+ * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ */
+ public int messageId;
+
+ /**
+ * Supported priority modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1)
+ */
+ public static final int PRIORITY_NORMAL = 0x0;
+ public static final int PRIORITY_INTERACTIVE = 0x1;
+ public static final int PRIORITY_URGENT = 0x2;
+ public static final int PRIORITY_EMERGENCY = 0x3;
+
+ public boolean priorityIndicatorSet = false;
+ public int priority = PRIORITY_NORMAL;
+
+ /**
+ * Supported privacy modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.10-1)
+ */
+ public static final int PRIVACY_NOT_RESTRICTED = 0x0;
+ public static final int PRIVACY_RESTRICTED = 0x1;
+ public static final int PRIVACY_CONFIDENTIAL = 0x2;
+ public static final int PRIVACY_SECRET = 0x3;
+
+ public boolean privacyIndicatorSet = false;
+ public int privacy = PRIVACY_NOT_RESTRICTED;
+
+ /**
+ * Supported alert priority modes for CDMA SMS messages
+ * (See 3GPP2 C.S0015-B, v2.0, table 4.5.13-1)
+ */
+ public static final int ALERT_DEFAULT = 0x0;
+ public static final int ALERT_LOW_PRIO = 0x1;
+ public static final int ALERT_MEDIUM_PRIO = 0x2;
+ public static final int ALERT_HIGH_PRIO = 0x3;
+
+ public boolean alertIndicatorSet = false;
+ public int alert = ALERT_DEFAULT;
+
+ /**
+ * Supported display modes for CDMA SMS messages. Display mode is
+ * a 2-bit value used to indicate to the mobile station when to
+ * display the received message. (See 3GPP2 C.S0015-B, v2,
+ * 4.5.16)
+ */
+ public static final int DISPLAY_MODE_IMMEDIATE = 0x0;
+ public static final int DISPLAY_MODE_DEFAULT = 0x1;
+ public static final int DISPLAY_MODE_USER = 0x2;
+
+ public boolean displayModeSet = false;
+ public int displayMode = DISPLAY_MODE_DEFAULT;
+
+ /**
+ * Language Indicator values. NOTE: the spec (3GPP2 C.S0015-B,
+ * v2, 4.5.14) is ambiguous as to the meaning of this field, as it
+ * refers to C.R1001-D but that reference has been crossed out.
+ * It would seem reasonable to assume the values from C.R1001-F
+ * (table 9.2-1) are to be used instead.
+ */
+ public static final int LANGUAGE_UNKNOWN = 0x00;
+ public static final int LANGUAGE_ENGLISH = 0x01;
+ public static final int LANGUAGE_FRENCH = 0x02;
+ public static final int LANGUAGE_SPANISH = 0x03;
+ public static final int LANGUAGE_JAPANESE = 0x04;
+ public static final int LANGUAGE_KOREAN = 0x05;
+ public static final int LANGUAGE_CHINESE = 0x06;
+ public static final int LANGUAGE_HEBREW = 0x07;
+
+ public boolean languageIndicatorSet = false;
+ public int language = LANGUAGE_UNKNOWN;
+
+ /**
+ * SMS Message Status Codes. The first component of the Message
+ * status indicates if an error has occurred and whether the error
+ * is considered permanent or temporary. The second component of
+ * the Message status indicates the cause of the error (if any).
+ * (See 3GPP2 C.S0015-B, v2.0, 4.5.21)
+ */
+ /* no-error codes */
+ public static final int ERROR_NONE = 0x00;
+ public static final int STATUS_ACCEPTED = 0x00;
+ public static final int STATUS_DEPOSITED_TO_INTERNET = 0x01;
+ public static final int STATUS_DELIVERED = 0x02;
+ public static final int STATUS_CANCELLED = 0x03;
+ /* temporary-error and permanent-error codes */
+ public static final int ERROR_TEMPORARY = 0x02;
+ public static final int STATUS_NETWORK_CONGESTION = 0x04;
+ public static final int STATUS_NETWORK_ERROR = 0x05;
+ public static final int STATUS_UNKNOWN_ERROR = 0x1F;
+ /* permanent-error codes */
+ public static final int ERROR_PERMANENT = 0x03;
+ public static final int STATUS_CANCEL_FAILED = 0x06;
+ public static final int STATUS_BLOCKED_DESTINATION = 0x07;
+ public static final int STATUS_TEXT_TOO_LONG = 0x08;
+ public static final int STATUS_DUPLICATE_MESSAGE = 0x09;
+ public static final int STATUS_INVALID_DESTINATION = 0x0A;
+ public static final int STATUS_MESSAGE_EXPIRED = 0x0D;
+ /* undefined-status codes */
+ public static final int ERROR_UNDEFINED = 0xFF;
+ public static final int STATUS_UNDEFINED = 0xFF;
+
+ public boolean messageStatusSet = false;
+ public int errorClass = ERROR_UNDEFINED;
+ public int messageStatus = STATUS_UNDEFINED;
+
+ /**
+ * 1-bit value that indicates whether a User Data Header (UDH) is present.
+ * (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ *
+ * NOTE: during encoding, this value will be set based on the
+ * presence of a UDH in the structured data, any existing setting
+ * will be overwritten.
+ */
+ public boolean hasUserDataHeader;
+
+ /**
+ * provides the information for the user data
+ * (e.g. padding bits, user data, user data header, etc)
+ * (See 3GPP2 C.S.0015-B, v2, 4.5.2)
+ */
+ public UserData userData;
+
+ /**
+ * The User Response Code subparameter is used in the SMS User
+ * Acknowledgment Message to respond to previously received short
+ * messages. This message center-specific element carries the
+ * identifier of a predefined response. (See 3GPP2 C.S.0015-B, v2,
+ * 4.5.3)
+ */
+ public boolean userResponseCodeSet = false;
+ public int userResponseCode;
+
+ /**
+ * 6-byte-field, see 3GPP2 C.S0015-B, v2, 4.5.4
+ */
+ public static class TimeStamp extends Time {
+
+ public TimeStamp() {
+ super(TimeZone.getDefault().getID()); // 3GPP2 timestamps use the local timezone
+ }
+
+ public static TimeStamp fromByteArray(byte[] data) {
+ TimeStamp ts = new TimeStamp();
+ // C.S0015-B v2.0, 4.5.4: range is 1996-2095
+ int year = IccUtils.cdmaBcdByteToInt(data[0]);
+ if (year > 99 || year < 0) return null;
+ ts.year = year >= 96 ? year + 1900 : year + 2000;
+ int month = IccUtils.cdmaBcdByteToInt(data[1]);
+ if (month < 1 || month > 12) return null;
+ ts.month = month - 1;
+ int day = IccUtils.cdmaBcdByteToInt(data[2]);
+ if (day < 1 || day > 31) return null;
+ ts.monthDay = day;
+ int hour = IccUtils.cdmaBcdByteToInt(data[3]);
+ if (hour < 0 || hour > 23) return null;
+ ts.hour = hour;
+ int minute = IccUtils.cdmaBcdByteToInt(data[4]);
+ if (minute < 0 || minute > 59) return null;
+ ts.minute = minute;
+ int second = IccUtils.cdmaBcdByteToInt(data[5]);
+ if (second < 0 || second > 59) return null;
+ ts.second = second;
+ return ts;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("TimeStamp ");
+ builder.append("{ year=" + year);
+ builder.append(", month=" + month);
+ builder.append(", day=" + monthDay);
+ builder.append(", hour=" + hour);
+ builder.append(", minute=" + minute);
+ builder.append(", second=" + second);
+ builder.append(" }");
+ return builder.toString();
+ }
+ }
+
+ public TimeStamp msgCenterTimeStamp;
+ public TimeStamp validityPeriodAbsolute;
+ public TimeStamp deferredDeliveryTimeAbsolute;
+
+ /**
+ * Relative time is specified as one byte, the value of which
+ * falls into a series of ranges, as specified below. The idea is
+ * that shorter time intervals allow greater precision -- the
+ * value means minutes from zero until the MINS_LIMIT (inclusive),
+ * upon which it means hours until the HOURS_LIMIT, and so
+ * forth. (See 3GPP2 C.S0015-B, v2, 4.5.6-1)
+ */
+ public static final int RELATIVE_TIME_MINS_LIMIT = 143;
+ public static final int RELATIVE_TIME_HOURS_LIMIT = 167;
+ public static final int RELATIVE_TIME_DAYS_LIMIT = 196;
+ public static final int RELATIVE_TIME_WEEKS_LIMIT = 244;
+ public static final int RELATIVE_TIME_INDEFINITE = 245;
+ public static final int RELATIVE_TIME_NOW = 246;
+ public static final int RELATIVE_TIME_MOBILE_INACTIVE = 247;
+ public static final int RELATIVE_TIME_RESERVED = 248;
+
+ public boolean validityPeriodRelativeSet;
+ public int validityPeriodRelative;
+ public boolean deferredDeliveryTimeRelativeSet;
+ public int deferredDeliveryTimeRelative;
+
+ /**
+ * The Reply Option subparameter contains 1-bit values which
+ * indicate whether SMS acknowledgment is requested or not. (See
+ * 3GPP2 C.S0015-B, v2, 4.5.11)
+ */
+ public boolean userAckReq;
+ public boolean deliveryAckReq;
+ public boolean readAckReq;
+ public boolean reportReq;
+
+ /**
+ * The Number of Messages subparameter (8-bit value) is a decimal
+ * number in the 0 to 99 range representing the number of messages
+ * stored at the Voice Mail System. This element is used by the
+ * Voice Mail Notification service. (See 3GPP2 C.S0015-B, v2,
+ * 4.5.12)
+ */
+ public int numberOfMessages;
+
+ /**
+ * The Message Deposit Index subparameter is assigned by the
+ * message center as a unique index to the contents of the User
+ * Data subparameter in each message sent to a particular mobile
+ * station. The mobile station, when replying to a previously
+ * received short message which included a Message Deposit Index
+ * subparameter, may include the Message Deposit Index of the
+ * received message to indicate to the message center that the
+ * original contents of the message are to be included in the
+ * reply. (See 3GPP2 C.S0015-B, v2, 4.5.18)
+ */
+ public int depositIndex;
+
+ /**
+ * 4-bit or 8-bit value that indicates the number to be dialed in reply to a
+ * received SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 4.5.15)
+ */
+ 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 ArrayList<CdmaSmsCbProgramData> serviceCategoryProgramData;
+
+ /**
+ * The Service Category Program Results subparameter informs the message center
+ * of the results of a Service Category Program Data request.
+ */
+ public ArrayList<CdmaSmsCbProgramResults> serviceCategoryProgramResults;
+
+
+ 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();
+ builder.append("BearerData ");
+ builder.append("{ messageType=" + messageType);
+ builder.append(", messageId=" + messageId);
+ builder.append(", priority=" + (priorityIndicatorSet ? priority : "unset"));
+ builder.append(", privacy=" + (privacyIndicatorSet ? privacy : "unset"));
+ builder.append(", alert=" + (alertIndicatorSet ? alert : "unset"));
+ builder.append(", displayMode=" + (displayModeSet ? displayMode : "unset"));
+ builder.append(", language=" + (languageIndicatorSet ? language : "unset"));
+ builder.append(", errorClass=" + (messageStatusSet ? errorClass : "unset"));
+ builder.append(", msgStatus=" + (messageStatusSet ? messageStatus : "unset"));
+ builder.append(", msgCenterTimeStamp=" +
+ ((msgCenterTimeStamp != null) ? msgCenterTimeStamp : "unset"));
+ builder.append(", validityPeriodAbsolute=" +
+ ((validityPeriodAbsolute != null) ? validityPeriodAbsolute : "unset"));
+ builder.append(", validityPeriodRelative=" +
+ ((validityPeriodRelativeSet) ? validityPeriodRelative : "unset"));
+ builder.append(", deferredDeliveryTimeAbsolute=" +
+ ((deferredDeliveryTimeAbsolute != null) ? deferredDeliveryTimeAbsolute : "unset"));
+ builder.append(", deferredDeliveryTimeRelative=" +
+ ((deferredDeliveryTimeRelativeSet) ? deferredDeliveryTimeRelative : "unset"));
+ builder.append(", userAckReq=" + userAckReq);
+ builder.append(", deliveryAckReq=" + deliveryAckReq);
+ builder.append(", readAckReq=" + readAckReq);
+ builder.append(", reportReq=" + reportReq);
+ builder.append(", numberOfMessages=" + numberOfMessages);
+ builder.append(", callbackNumber=" + Rlog.pii(LOG_TAG, callbackNumber));
+ builder.append(", depositIndex=" + depositIndex);
+ builder.append(", hasUserDataHeader=" + hasUserDataHeader);
+ builder.append(", userData=" + userData);
+ builder.append(" }");
+ return builder.toString();
+ }
+
+ private static void encodeMessageId(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 3);
+ outStream.write(4, bData.messageType);
+ outStream.write(8, bData.messageId >> 8);
+ outStream.write(8, bData.messageId);
+ outStream.write(1, bData.hasUserDataHeader ? 1 : 0);
+ outStream.skip(3);
+ }
+
+ private static int countAsciiSeptets(CharSequence msg, boolean force) {
+ int msgLen = msg.length();
+ if (force) return msgLen;
+ for (int i = 0; i < msgLen; i++) {
+ if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) {
+ return -1;
+ }
+ }
+ return msgLen;
+ }
+
+ /**
+ * Calculate the message text encoding length, fragmentation, and other details.
+ *
+ * @param msg message text
+ * @param force7BitEncoding ignore (but still count) illegal characters if true
+ * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
+ * @return septet count, or -1 on failure
+ */
+ public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg,
+ boolean force7BitEncoding, boolean isEntireMsg) {
+ TextEncodingDetails ted;
+ int septets = countAsciiSeptets(msg, force7BitEncoding);
+ if (septets != -1 && septets <= SmsConstants.MAX_USER_DATA_SEPTETS) {
+ ted = new TextEncodingDetails();
+ ted.msgCount = 1;
+ ted.codeUnitCount = septets;
+ ted.codeUnitsRemaining = SmsConstants.MAX_USER_DATA_SEPTETS - septets;
+ ted.codeUnitSize = SmsConstants.ENCODING_7BIT;
+ } else {
+ ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength(
+ msg, force7BitEncoding);
+ if (ted.msgCount == 1 && ted.codeUnitSize == SmsConstants.ENCODING_7BIT &&
+ isEntireMsg) {
+ // We don't support single-segment EMS, so calculate for 16-bit
+ // TODO: Consider supporting single-segment EMS
+ return SmsMessageBase.calcUnicodeEncodingDetails(msg);
+ }
+ }
+ return ted;
+ }
+
+ private static byte[] encode7bitAscii(String msg, boolean force)
+ throws CodingException
+ {
+ try {
+ BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length());
+ int msgLen = msg.length();
+ for (int i = 0; i < msgLen; i++) {
+ int charCode = UserData.charToAscii.get(msg.charAt(i), -1);
+ if (charCode == -1) {
+ if (force) {
+ outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR);
+ } else {
+ throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")");
+ }
+ } else {
+ outStream.write(7, charCode);
+ }
+ }
+ return outStream.toByteArray();
+ } catch (BitwiseOutputStream.AccessException ex) {
+ throw new CodingException("7bit ASCII encode failed: " + ex);
+ }
+ }
+
+ private static byte[] encodeUtf16(String msg)
+ throws CodingException
+ {
+ try {
+ return msg.getBytes("utf-16be");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("UTF-16 encode failed: " + ex);
+ }
+ }
+
+ private static class Gsm7bitCodingResult {
+ int septets;
+ byte[] data;
+ }
+
+ private static Gsm7bitCodingResult encode7bitGsm(String msg, int septetOffset, boolean force)
+ throws CodingException
+ {
+ try {
+ /*
+ * TODO(cleanup): It would be nice if GsmAlphabet provided
+ * an option to produce just the data without prepending
+ * the septet count, as this function is really just a
+ * wrapper to strip that off. Not to mention that the
+ * septet count is generally known prior to invocation of
+ * the encoder. Note that it cannot be derived from the
+ * resulting array length, since that cannot distinguish
+ * if the last contains either 1 or 8 valid bits.
+ *
+ * TODO(cleanup): The BitwiseXStreams could also be
+ * extended with byte-wise reversed endianness read/write
+ * routines to allow a corresponding implementation of
+ * stringToGsm7BitPacked, and potentially directly support
+ * access to the main bitwise stream from encode/decode.
+ */
+ byte[] fullData = GsmAlphabet.stringToGsm7BitPacked(msg, septetOffset, !force, 0, 0);
+ Gsm7bitCodingResult result = new Gsm7bitCodingResult();
+ result.data = new byte[fullData.length - 1];
+ System.arraycopy(fullData, 1, result.data, 0, fullData.length - 1);
+ result.septets = fullData[0] & 0x00FF;
+ return result;
+ } catch (com.android.internal.telephony.EncodeException ex) {
+ throw new CodingException("7bit GSM encode failed: " + ex);
+ }
+ }
+
+ private static void encode7bitEms(UserData uData, byte[] udhData, boolean force)
+ throws CodingException
+ {
+ int udhBytes = udhData.length + 1; // Add length octet.
+ int udhSeptets = ((udhBytes * 8) + 6) / 7;
+ Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, udhSeptets, force);
+ uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+ uData.msgEncodingSet = true;
+ uData.numFields = gcr.septets;
+ uData.payload = gcr.data;
+ uData.payload[0] = (byte)udhData.length;
+ System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
+ }
+
+ private static void encode16bitEms(UserData uData, byte[] udhData)
+ throws CodingException
+ {
+ byte[] payload = encodeUtf16(uData.payloadStr);
+ int udhBytes = udhData.length + 1; // Add length octet.
+ int udhCodeUnits = (udhBytes + 1) / 2;
+ int payloadCodeUnits = payload.length / 2;
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ uData.msgEncodingSet = true;
+ uData.numFields = udhCodeUnits + payloadCodeUnits;
+ uData.payload = new byte[uData.numFields * 2];
+ uData.payload[0] = (byte)udhData.length;
+ System.arraycopy(udhData, 0, uData.payload, 1, udhData.length);
+ System.arraycopy(payload, 0, uData.payload, udhBytes, payload.length);
+ }
+
+ private static void encodeEmsUserDataPayload(UserData uData)
+ throws CodingException
+ {
+ byte[] headerData = SmsHeader.toByteArray(uData.userDataHeader);
+ if (uData.msgEncodingSet) {
+ if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
+ encode7bitEms(uData, headerData, true);
+ } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
+ encode16bitEms(uData, headerData);
+ } else {
+ throw new CodingException("unsupported EMS user data encoding (" +
+ uData.msgEncoding + ")");
+ }
+ } else {
+ try {
+ encode7bitEms(uData, headerData, false);
+ } catch (CodingException ex) {
+ encode16bitEms(uData, headerData);
+ }
+ }
+ }
+
+ private static byte[] encodeShiftJis(String msg) throws CodingException {
+ try {
+ return msg.getBytes("Shift_JIS");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("Shift-JIS encode failed: " + ex);
+ }
+ }
+
+ private static void encodeUserDataPayload(UserData uData)
+ throws CodingException
+ {
+ if ((uData.payloadStr == null) && (uData.msgEncoding != UserData.ENCODING_OCTET)) {
+ Rlog.e(LOG_TAG, "user data with null payloadStr");
+ uData.payloadStr = "";
+ }
+
+ if (uData.userDataHeader != null) {
+ encodeEmsUserDataPayload(uData);
+ return;
+ }
+
+ if (uData.msgEncodingSet) {
+ if (uData.msgEncoding == UserData.ENCODING_OCTET) {
+ if (uData.payload == null) {
+ Rlog.e(LOG_TAG, "user data with octet encoding but null payload");
+ uData.payload = new byte[0];
+ uData.numFields = 0;
+ } else {
+ uData.numFields = uData.payload.length;
+ }
+ } else {
+ if (uData.payloadStr == null) {
+ Rlog.e(LOG_TAG, "non-octet user data with null payloadStr");
+ uData.payloadStr = "";
+ }
+ if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) {
+ Gsm7bitCodingResult gcr = encode7bitGsm(uData.payloadStr, 0, true);
+ uData.payload = gcr.data;
+ uData.numFields = gcr.septets;
+ } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) {
+ uData.payload = encode7bitAscii(uData.payloadStr, true);
+ uData.numFields = uData.payloadStr.length();
+ } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) {
+ uData.payload = encodeUtf16(uData.payloadStr);
+ uData.numFields = uData.payloadStr.length();
+ } else if (uData.msgEncoding == UserData.ENCODING_SHIFT_JIS) {
+ uData.payload = encodeShiftJis(uData.payloadStr);
+ uData.numFields = uData.payload.length;
+ } else {
+ throw new CodingException("unsupported user data encoding (" +
+ uData.msgEncoding + ")");
+ }
+ }
+ } else {
+ try {
+ uData.payload = encode7bitAscii(uData.payloadStr, false);
+ uData.msgEncoding = UserData.ENCODING_7BIT_ASCII;
+ } catch (CodingException ex) {
+ uData.payload = encodeUtf16(uData.payloadStr);
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ }
+ uData.numFields = uData.payloadStr.length();
+ uData.msgEncodingSet = true;
+ }
+ }
+
+ private static void encodeUserData(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException, CodingException
+ {
+ /*
+ * TODO(cleanup): Do we really need to set userData.payload as
+ * a side effect of encoding? If not, we could avoid data
+ * copies by passing outStream directly.
+ */
+ encodeUserDataPayload(bData.userData);
+ bData.hasUserDataHeader = bData.userData.userDataHeader != null;
+
+ if (bData.userData.payload.length > SmsConstants.MAX_USER_DATA_BYTES) {
+ throw new CodingException("encoded user data too large (" +
+ bData.userData.payload.length +
+ " > " + SmsConstants.MAX_USER_DATA_BYTES + " bytes)");
+ }
+
+ /*
+ * TODO(cleanup): figure out what the right answer is WRT paddingBits field
+ *
+ * userData.paddingBits = (userData.payload.length * 8) - (userData.numFields * 7);
+ * userData.paddingBits = 0; // XXX this seems better, but why?
+ *
+ */
+ int dataBits = (bData.userData.payload.length * 8) - bData.userData.paddingBits;
+ int paramBits = dataBits + 13;
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ paramBits += 8;
+ }
+ int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
+ int paddingBits = (paramBytes * 8) - paramBits;
+ outStream.write(8, paramBytes);
+ outStream.write(5, bData.userData.msgEncoding);
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ outStream.write(8, bData.userData.msgType);
+ }
+ outStream.write(8, bData.userData.numFields);
+ outStream.writeByteArray(dataBits, bData.userData.payload);
+ if (paddingBits > 0) outStream.write(paddingBits, 0);
+ }
+
+ private static void encodeReplyOption(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(1, bData.userAckReq ? 1 : 0);
+ outStream.write(1, bData.deliveryAckReq ? 1 : 0);
+ outStream.write(1, bData.readAckReq ? 1 : 0);
+ outStream.write(1, bData.reportReq ? 1 : 0);
+ outStream.write(4, 0);
+ }
+
+ private static byte[] encodeDtmfSmsAddress(String address) {
+ int digits = address.length();
+ int dataBits = digits * 4;
+ int dataBytes = (dataBits / 8);
+ dataBytes += (dataBits % 8) > 0 ? 1 : 0;
+ byte[] rawData = new byte[dataBytes];
+ for (int i = 0; i < digits; i++) {
+ char c = address.charAt(i);
+ int val = 0;
+ if ((c >= '1') && (c <= '9')) val = c - '0';
+ else if (c == '0') val = 10;
+ else if (c == '*') val = 11;
+ else if (c == '#') val = 12;
+ else return null;
+ rawData[i / 2] |= val << (4 - ((i % 2) * 4));
+ }
+ return rawData;
+ }
+
+ /*
+ * TODO(cleanup): CdmaSmsAddress encoding should make use of
+ * CdmaSmsAddress.parse provided that DTMF encoding is unified,
+ * and the difference in 4-bit vs. 8-bit is resolved.
+ */
+
+ private static void encodeCdmaSmsAddress(CdmaSmsAddress addr) throws CodingException {
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ try {
+ addr.origBytes = addr.address.getBytes("US-ASCII");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("invalid SMS address, cannot convert to ASCII");
+ }
+ } else {
+ addr.origBytes = encodeDtmfSmsAddress(addr.address);
+ }
+ }
+
+ private static void encodeCallbackNumber(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException, CodingException
+ {
+ CdmaSmsAddress addr = bData.callbackNumber;
+ encodeCdmaSmsAddress(addr);
+ int paramBits = 9;
+ int dataBits = 0;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ paramBits += 7;
+ dataBits = addr.numberOfDigits * 8;
+ } else {
+ dataBits = addr.numberOfDigits * 4;
+ }
+ paramBits += dataBits;
+ int paramBytes = (paramBits / 8) + ((paramBits % 8) > 0 ? 1 : 0);
+ int paddingBits = (paramBytes * 8) - paramBits;
+ outStream.write(8, paramBytes);
+ outStream.write(1, addr.digitMode);
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ outStream.write(3, addr.ton);
+ outStream.write(4, addr.numberPlan);
+ }
+ outStream.write(8, addr.numberOfDigits);
+ outStream.writeByteArray(dataBits, addr.origBytes);
+ if (paddingBits > 0) outStream.write(paddingBits, 0);
+ }
+
+ private static void encodeMsgStatus(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.errorClass);
+ outStream.write(6, bData.messageStatus);
+ }
+
+ private static void encodeMsgCount(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.numberOfMessages);
+ }
+
+ private static void encodeValidityPeriodRel(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.validityPeriodRelative);
+ }
+
+ private static void encodePrivacyIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.privacy);
+ outStream.skip(6);
+ }
+
+ private static void encodeLanguageIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(8, bData.language);
+ }
+
+ private static void encodeDisplayMode(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.displayMode);
+ outStream.skip(6);
+ }
+
+ private static void encodePriorityIndicator(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.priority);
+ outStream.skip(6);
+ }
+
+ private static void encodeMsgDeliveryAlert(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ outStream.write(8, 1);
+ outStream.write(2, bData.alert);
+ outStream.skip(6);
+ }
+
+ private static void encodeScpResults(BearerData bData, BitwiseOutputStream outStream)
+ throws BitwiseOutputStream.AccessException
+ {
+ ArrayList<CdmaSmsCbProgramResults> results = bData.serviceCategoryProgramResults;
+ outStream.write(8, (results.size() * 4)); // 4 octets per program result
+ for (CdmaSmsCbProgramResults result : results) {
+ int category = result.getCategory();
+ outStream.write(8, category >> 8);
+ outStream.write(8, category);
+ outStream.write(8, result.getLanguage());
+ outStream.write(4, result.getCategoryResult());
+ outStream.skip(4);
+ }
+ }
+
+ /**
+ * Create serialized representation for BearerData object.
+ * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+ *
+ * @param bData an instance of BearerData.
+ *
+ * @return byte array of raw encoded SMS bearer data.
+ */
+ public static byte[] encode(BearerData bData) {
+ bData.hasUserDataHeader = ((bData.userData != null) &&
+ (bData.userData.userDataHeader != null));
+ try {
+ BitwiseOutputStream outStream = new BitwiseOutputStream(200);
+ outStream.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
+ encodeMessageId(bData, outStream);
+ if (bData.userData != null) {
+ outStream.write(8, SUBPARAM_USER_DATA);
+ encodeUserData(bData, outStream);
+ }
+ if (bData.callbackNumber != null) {
+ outStream.write(8, SUBPARAM_CALLBACK_NUMBER);
+ encodeCallbackNumber(bData, outStream);
+ }
+ if (bData.userAckReq || bData.deliveryAckReq || bData.readAckReq || bData.reportReq) {
+ outStream.write(8, SUBPARAM_REPLY_OPTION);
+ encodeReplyOption(bData, outStream);
+ }
+ if (bData.numberOfMessages != 0) {
+ outStream.write(8, SUBPARAM_NUMBER_OF_MESSAGES);
+ encodeMsgCount(bData, outStream);
+ }
+ if (bData.validityPeriodRelativeSet) {
+ outStream.write(8, SUBPARAM_VALIDITY_PERIOD_RELATIVE);
+ encodeValidityPeriodRel(bData, outStream);
+ }
+ if (bData.privacyIndicatorSet) {
+ outStream.write(8, SUBPARAM_PRIVACY_INDICATOR);
+ encodePrivacyIndicator(bData, outStream);
+ }
+ if (bData.languageIndicatorSet) {
+ outStream.write(8, SUBPARAM_LANGUAGE_INDICATOR);
+ encodeLanguageIndicator(bData, outStream);
+ }
+ if (bData.displayModeSet) {
+ outStream.write(8, SUBPARAM_MESSAGE_DISPLAY_MODE);
+ encodeDisplayMode(bData, outStream);
+ }
+ if (bData.priorityIndicatorSet) {
+ outStream.write(8, SUBPARAM_PRIORITY_INDICATOR);
+ encodePriorityIndicator(bData, outStream);
+ }
+ if (bData.alertIndicatorSet) {
+ outStream.write(8, SUBPARAM_ALERT_ON_MESSAGE_DELIVERY);
+ encodeMsgDeliveryAlert(bData, outStream);
+ }
+ if (bData.messageStatusSet) {
+ outStream.write(8, SUBPARAM_MESSAGE_STATUS);
+ encodeMsgStatus(bData, outStream);
+ }
+ if (bData.serviceCategoryProgramResults != null) {
+ outStream.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS);
+ encodeScpResults(bData, outStream);
+ }
+ return outStream.toByteArray();
+ } catch (BitwiseOutputStream.AccessException ex) {
+ Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
+ } catch (CodingException ex) {
+ Rlog.e(LOG_TAG, "BearerData encode failed: " + ex);
+ }
+ return null;
+ }
+
+ private static boolean decodeMessageId(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 3 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.messageType = inStream.read(4);
+ bData.messageId = inStream.read(8) << 8;
+ bData.messageId |= inStream.read(8);
+ bData.hasUserDataHeader = (inStream.read(1) == 1);
+ inStream.skip(3);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeReserved(
+ BearerData bData, BitwiseInputStream inStream, int subparamId)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ boolean decodeSuccess = false;
+ int subparamLen = inStream.read(8); // SUBPARAM_LEN
+ int paramBits = subparamLen * 8;
+ if (paramBits <= inStream.available()) {
+ decodeSuccess = true;
+ inStream.skip(paramBits);
+ }
+ Rlog.d(LOG_TAG, "RESERVED bearer data subparameter " + subparamId + " decode "
+ + (decodeSuccess ? "succeeded" : "failed") + " (param bits = " + paramBits + ")");
+ if (!decodeSuccess) {
+ throw new CodingException("RESERVED bearer data subparameter " + subparamId
+ + " had invalid SUBPARAM_LEN " + subparamLen);
+ }
+
+ return decodeSuccess;
+ }
+
+ private static boolean decodeUserData(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException
+ {
+ int paramBits = inStream.read(8) * 8;
+ bData.userData = new UserData();
+ bData.userData.msgEncoding = inStream.read(5);
+ bData.userData.msgEncodingSet = true;
+ bData.userData.msgType = 0;
+ int consumedBits = 5;
+ if ((bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) ||
+ (bData.userData.msgEncoding == UserData.ENCODING_GSM_DCS)) {
+ bData.userData.msgType = inStream.read(8);
+ consumedBits += 8;
+ }
+ bData.userData.numFields = inStream.read(8);
+ consumedBits += 8;
+ int dataBits = paramBits - consumedBits;
+ bData.userData.payload = inStream.readByteArray(dataBits);
+ return true;
+ }
+
+ private static String decodeUtf8(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ return decodeCharset(data, offset, numFields, 1, "UTF-8");
+ }
+
+ private static String decodeUtf16(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ // Subtract header and possible padding byte (at end) from num fields.
+ int padding = offset % 2;
+ numFields -= (offset + padding) / 2;
+ return decodeCharset(data, offset, numFields, 2, "utf-16be");
+ }
+
+ private static String decodeCharset(byte[] data, int offset, int numFields, int width,
+ String charset) throws CodingException
+ {
+ if (numFields < 0 || (numFields * width + offset) > data.length) {
+ // Try to decode the max number of characters in payload
+ int padding = offset % width;
+ int maxNumFields = (data.length - offset - padding) / width;
+ if (maxNumFields < 0) {
+ throw new CodingException(charset + " decode failed: offset out of range");
+ }
+ Rlog.e(LOG_TAG, charset + " decode error: offset = " + offset + " numFields = "
+ + numFields + " data.length = " + data.length + " maxNumFields = "
+ + maxNumFields);
+ numFields = maxNumFields;
+ }
+ try {
+ return new String(data, offset, numFields * width, charset);
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException(charset + " decode failed: " + ex);
+ }
+ }
+
+ private static String decode7bitAscii(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ try {
+ offset *= 8;
+ StringBuffer strBuf = new StringBuffer(numFields);
+ BitwiseInputStream inStream = new BitwiseInputStream(data);
+ int wantedBits = (offset * 8) + (numFields * 7);
+ if (inStream.available() < wantedBits) {
+ throw new CodingException("insufficient data (wanted " + wantedBits +
+ " bits, but only have " + inStream.available() + ")");
+ }
+ inStream.skip(offset);
+ for (int i = 0; i < numFields; i++) {
+ int charCode = inStream.read(7);
+ if ((charCode >= UserData.ASCII_MAP_BASE_INDEX) &&
+ (charCode <= UserData.ASCII_MAP_MAX_INDEX)) {
+ strBuf.append(UserData.ASCII_MAP[charCode - UserData.ASCII_MAP_BASE_INDEX]);
+ } else if (charCode == UserData.ASCII_NL_INDEX) {
+ strBuf.append('\n');
+ } else if (charCode == UserData.ASCII_CR_INDEX) {
+ strBuf.append('\r');
+ } else {
+ /* For other charCodes, they are unprintable, and so simply use SPACE. */
+ strBuf.append(' ');
+ }
+ }
+ return strBuf.toString();
+ } catch (BitwiseInputStream.AccessException ex) {
+ throw new CodingException("7bit ASCII decode failed: " + ex);
+ }
+ }
+
+ private static String decode7bitGsm(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ // Start reading from the next 7-bit aligned boundary after offset.
+ int offsetBits = offset * 8;
+ int offsetSeptets = (offsetBits + 6) / 7;
+ numFields -= offsetSeptets;
+ int paddingBits = (offsetSeptets * 7) - offsetBits;
+ String result = GsmAlphabet.gsm7BitPackedToString(data, offset, numFields, paddingBits,
+ 0, 0);
+ if (result == null) {
+ throw new CodingException("7bit GSM decoding failed");
+ }
+ return result;
+ }
+
+ private static String decodeLatin(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ return decodeCharset(data, offset, numFields, 1, "ISO-8859-1");
+ }
+
+ private static String decodeShiftJis(byte[] data, int offset, int numFields)
+ throws CodingException
+ {
+ return decodeCharset(data, offset, numFields, 1, "Shift_JIS");
+ }
+
+ private static String decodeGsmDcs(byte[] data, int offset, int numFields, int msgType)
+ throws CodingException
+ {
+ if ((msgType & 0xC0) != 0) {
+ throw new CodingException("unsupported coding group ("
+ + msgType + ")");
+ }
+
+ switch ((msgType >> 2) & 0x3) {
+ case UserData.ENCODING_GSM_DCS_7BIT:
+ return decode7bitGsm(data, offset, numFields);
+ case UserData.ENCODING_GSM_DCS_8BIT:
+ return decodeUtf8(data, offset, numFields);
+ case UserData.ENCODING_GSM_DCS_16BIT:
+ return decodeUtf16(data, offset, numFields);
+ default:
+ throw new CodingException("unsupported user msgType encoding ("
+ + msgType + ")");
+ }
+ }
+
+ private static void decodeUserDataPayload(UserData userData, boolean hasUserDataHeader)
+ throws CodingException
+ {
+ int offset = 0;
+ if (hasUserDataHeader) {
+ int udhLen = userData.payload[0] & 0x00FF;
+ offset += udhLen + 1;
+ byte[] headerData = new byte[udhLen];
+ System.arraycopy(userData.payload, 1, headerData, 0, udhLen);
+ userData.userDataHeader = SmsHeader.fromByteArray(headerData);
+ }
+ switch (userData.msgEncoding) {
+ case UserData.ENCODING_OCTET:
+ /*
+ * Octet decoding depends on the carrier service.
+ */
+ boolean decodingtypeUTF8 = Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_sms_utf8_support);
+
+ // Strip off any padding bytes, meaning any differences between the length of the
+ // array and the target length specified by numFields. This is to avoid any
+ // confusion by code elsewhere that only considers the payload array length.
+ byte[] payload = new byte[userData.numFields];
+ int copyLen = userData.numFields < userData.payload.length
+ ? userData.numFields : userData.payload.length;
+
+ System.arraycopy(userData.payload, 0, payload, 0, copyLen);
+ userData.payload = payload;
+
+ if (!decodingtypeUTF8) {
+ // There are many devices in the market that send 8bit text sms (latin encoded) as
+ // octet encoded.
+ userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+ } else {
+ 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);
+ break;
+ case UserData.ENCODING_UNICODE_16:
+ userData.payloadStr = decodeUtf16(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_GSM_7BIT_ALPHABET:
+ userData.payloadStr = decode7bitGsm(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_LATIN:
+ userData.payloadStr = decodeLatin(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_SHIFT_JIS:
+ userData.payloadStr = decodeShiftJis(userData.payload, offset, userData.numFields);
+ break;
+ case UserData.ENCODING_GSM_DCS:
+ userData.payloadStr = decodeGsmDcs(userData.payload, offset,
+ userData.numFields, userData.msgType);
+ break;
+ default:
+ throw new CodingException("unsupported user data encoding ("
+ + userData.msgEncoding + ")");
+ }
+ }
+
+ /**
+ * IS-91 Voice Mail message decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ * (For character encodings, see TIA/EIA/IS-91, Annex B)
+ *
+ * Protocol Summary: The user data payload may contain 3-14
+ * characters. The first two characters are parsed as a number
+ * and indicate the number of voicemails. The third character is
+ * either a SPACE or '!' to indicate normal or urgent priority,
+ * respectively. Any following characters are treated as normal
+ * text user data payload.
+ *
+ * Note that the characters encoding is 6-bit packed.
+ */
+ private static void decodeIs91VoicemailStatus(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ 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 < 3) || (dataLen < numFields)) {
+ throw new CodingException("IS-91 voicemail status decoding failed");
+ }
+ try {
+ StringBuffer strbuf = new StringBuffer(dataLen);
+ while (inStream.available() >= 6) {
+ strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
+ }
+ String data = strbuf.toString();
+ bData.numberOfMessages = Integer.parseInt(data.substring(0, 2));
+ char prioCode = data.charAt(2);
+ if (prioCode == ' ') {
+ bData.priority = PRIORITY_NORMAL;
+ } else if (prioCode == '!') {
+ bData.priority = PRIORITY_URGENT;
+ } else {
+ throw new CodingException("IS-91 voicemail status decoding failed: " +
+ "illegal priority setting (" + prioCode + ")");
+ }
+ bData.priorityIndicatorSet = true;
+ bData.userData.payloadStr = data.substring(3, numFields - 3);
+ } catch (java.lang.NumberFormatException ex) {
+ throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
+ } catch (java.lang.IndexOutOfBoundsException ex) {
+ throw new CodingException("IS-91 voicemail status decoding failed: " + ex);
+ }
+ }
+
+ /**
+ * IS-91 Short Message decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ * (For character encodings, see TIA/EIA/IS-91, Annex B)
+ *
+ * Protocol Summary: The user data payload may contain 1-14
+ * characters, which are treated as normal text user data payload.
+ * Note that the characters encoding is 6-bit packed.
+ */
+ private static void decodeIs91ShortMessage(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ int dataLen = inStream.available() / 6; // 6-bit packed character encoding.
+ int numFields = bData.userData.numFields;
+ // 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++) {
+ strbuf.append(UserData.ASCII_MAP[inStream.read(6)]);
+ }
+ bData.userData.payloadStr = strbuf.toString();
+ }
+
+ /**
+ * IS-91 CLI message (callback number) decoding
+ * (See 3GPP2 C.S0015-A, Table 4.3.1.4.1-1)
+ *
+ * Protocol Summary: The data payload may contain 1-32 digits,
+ * encoded using standard 4-bit DTMF, which are treated as a
+ * callback number.
+ */
+ private static void decodeIs91Cli(BearerData bData) throws CodingException {
+ BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+ int dataLen = inStream.available() / 4; // 4-bit packed DTMF digit encoding.
+ int numFields = bData.userData.numFields;
+ if ((dataLen > 14) || (dataLen < 3) || (dataLen < numFields)) {
+ throw new CodingException("IS-91 voicemail status decoding failed");
+ }
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.digitMode = CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF;
+ addr.origBytes = bData.userData.payload;
+ addr.numberOfDigits = (byte)numFields;
+ decodeSmsAddress(addr);
+ bData.callbackNumber = addr;
+ }
+
+ private static void decodeIs91(BearerData bData)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ switch (bData.userData.msgType) {
+ case UserData.IS91_MSG_TYPE_VOICEMAIL_STATUS:
+ decodeIs91VoicemailStatus(bData);
+ break;
+ case UserData.IS91_MSG_TYPE_CLI:
+ decodeIs91Cli(bData);
+ break;
+ case UserData.IS91_MSG_TYPE_SHORT_MESSAGE_FULL:
+ case UserData.IS91_MSG_TYPE_SHORT_MESSAGE:
+ decodeIs91ShortMessage(bData);
+ break;
+ default:
+ throw new CodingException("unsupported IS-91 message type (" +
+ bData.userData.msgType + ")");
+ }
+ }
+
+ private static boolean decodeReplyOption(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.userAckReq = (inStream.read(1) == 1);
+ bData.deliveryAckReq = (inStream.read(1) == 1);
+ bData.readAckReq = (inStream.read(1) == 1);
+ bData.reportReq = (inStream.read(1) == 1);
+ inStream.skip(4);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "REPLY_OPTION decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgCount(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDepositIndex(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 2 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static String decodeDtmfSmsAddress(byte[] rawData, int numFields)
+ throws CodingException
+ {
+ /* DTMF 4-bit digit encoding, defined in at
+ * 3GPP2 C.S005-D, v2.0, table 2.7.1.3.2.4-4 */
+ StringBuffer strBuf = new StringBuffer(numFields);
+ for (int i = 0; i < numFields; i++) {
+ int val = 0x0F & (rawData[i / 2] >>> (4 - ((i % 2) * 4)));
+ if ((val >= 1) && (val <= 9)) strBuf.append(Integer.toString(val, 10));
+ else if (val == 10) strBuf.append('0');
+ else if (val == 11) strBuf.append('*');
+ else if (val == 12) strBuf.append('#');
+ else throw new CodingException("invalid SMS address DTMF code (" + val + ")");
+ }
+ return strBuf.toString();
+ }
+
+ private static void decodeSmsAddress(CdmaSmsAddress addr) throws CodingException {
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ try {
+ /* As specified in 3GPP2 C.S0015-B, v2, 4.5.15 -- actually
+ * just 7-bit ASCII encoding, with the MSB being zero. */
+ addr.address = new String(addr.origBytes, 0, addr.origBytes.length, "US-ASCII");
+ } catch (java.io.UnsupportedEncodingException ex) {
+ throw new CodingException("invalid SMS address ASCII code");
+ }
+ } else {
+ addr.address = decodeDtmfSmsAddress(addr.origBytes, addr.numberOfDigits);
+ }
+ }
+
+ private static boolean decodeCallbackNumber(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException, CodingException
+ {
+ final int EXPECTED_PARAM_SIZE = 1 * 8; //at least
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits < EXPECTED_PARAM_SIZE) {
+ inStream.skip(paramBits);
+ return false;
+ }
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.digitMode = inStream.read(1);
+ byte fieldBits = 4;
+ byte consumedBits = 1;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ addr.ton = inStream.read(3);
+ addr.numberPlan = inStream.read(4);
+ fieldBits = 8;
+ consumedBits += 7;
+ }
+ addr.numberOfDigits = inStream.read(8);
+ consumedBits += 8;
+ int remainingBits = paramBits - consumedBits;
+ int dataBits = addr.numberOfDigits * fieldBits;
+ int paddingBits = remainingBits - dataBits;
+ if (remainingBits < dataBits) {
+ throw new CodingException("CALLBACK_NUMBER subparam encoding size error (" +
+ "remainingBits + " + remainingBits + ", dataBits + " +
+ dataBits + ", paddingBits + " + paddingBits + ")");
+ }
+ addr.origBytes = inStream.readByteArray(dataBits);
+ inStream.skip(paddingBits);
+ decodeSmsAddress(addr);
+ bData.callbackNumber = addr;
+ return true;
+ }
+
+ private static boolean decodeMsgStatus(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.errorClass = inStream.read(2);
+ bData.messageStatus = inStream.read(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.messageStatusSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgCenterTimeStamp(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeValidityAbs(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDeferredDeliveryAbs(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 6 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray(
+ inStream.readByteArray(6 * 8));
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ return decodeSuccess;
+ }
+
+ private static boolean decodeValidityRel(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.deferredDeliveryTimeRelative = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.deferredDeliveryTimeRelativeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDeferredDeliveryRel(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.validityPeriodRelative = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.validityPeriodRelativeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodePrivacyIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.privacy = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.privacyIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeLanguageIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.language = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.languageIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeDisplayMode(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.displayMode = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "DISPLAY_MODE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.displayModeSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodePriorityIndicator(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.priority = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.priorityIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeMsgDeliveryAlert(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.alert = inStream.read(2);
+ inStream.skip(6);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.alertIndicatorSet = decodeSuccess;
+ return decodeSuccess;
+ }
+
+ private static boolean decodeUserResponseCode(BearerData bData, BitwiseInputStream inStream)
+ throws BitwiseInputStream.AccessException {
+ final int EXPECTED_PARAM_SIZE = 1 * 8;
+ boolean decodeSuccess = false;
+ int paramBits = inStream.read(8) * 8;
+ if (paramBits >= EXPECTED_PARAM_SIZE) {
+ paramBits -= EXPECTED_PARAM_SIZE;
+ decodeSuccess = true;
+ bData.userResponseCode = inStream.read(8);
+ }
+ if ((! decodeSuccess) || (paramBits > 0)) {
+ Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
+ (decodeSuccess ? "succeeded" : "failed") +
+ " (extra bits = " + paramBits + ")");
+ }
+ inStream.skip(paramBits);
+ bData.userResponseCodeSet = decodeSuccess;
+ 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);
+ int language = 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)) {
+ Rlog.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:
+ Rlog.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) {
+ int subparamId = inStream.read(8);
+ int subparamIdBit = 1 << subparamId;
+ // int is 4 bytes. This duplicate check has a limit to Id number up to 32 (4*8)
+ // as 32th bit is the max bit in int.
+ // Per 3GPP2 C.S0015-B Table 4.5-1 Bearer Data Subparameter Identifiers:
+ // last defined subparam ID is 23 (00010111 = 0x17 = 23).
+ // Only do duplicate subparam ID check if subparam is within defined value as
+ // reserved subparams are just skipped.
+ if ((foundSubparamMask & subparamIdBit) != 0 &&
+ (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
+ subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+ throw new CodingException("illegal duplicate subparameter (" +
+ subparamId + ")");
+ }
+ boolean decodeSuccess;
+ switch (subparamId) {
+ case SUBPARAM_MESSAGE_IDENTIFIER:
+ decodeSuccess = decodeMessageId(bData, inStream);
+ break;
+ case SUBPARAM_USER_DATA:
+ decodeSuccess = decodeUserData(bData, inStream);
+ break;
+ case SUBPARAM_USER_RESPONSE_CODE:
+ decodeSuccess = decodeUserResponseCode(bData, inStream);
+ break;
+ case SUBPARAM_REPLY_OPTION:
+ decodeSuccess = decodeReplyOption(bData, inStream);
+ break;
+ case SUBPARAM_NUMBER_OF_MESSAGES:
+ decodeSuccess = decodeMsgCount(bData, inStream);
+ break;
+ case SUBPARAM_CALLBACK_NUMBER:
+ decodeSuccess = decodeCallbackNumber(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_STATUS:
+ decodeSuccess = decodeMsgStatus(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_CENTER_TIME_STAMP:
+ decodeSuccess = decodeMsgCenterTimeStamp(bData, inStream);
+ break;
+ case SUBPARAM_VALIDITY_PERIOD_ABSOLUTE:
+ decodeSuccess = decodeValidityAbs(bData, inStream);
+ break;
+ case SUBPARAM_VALIDITY_PERIOD_RELATIVE:
+ decodeSuccess = decodeValidityRel(bData, inStream);
+ break;
+ case SUBPARAM_DEFERRED_DELIVERY_TIME_ABSOLUTE:
+ decodeSuccess = decodeDeferredDeliveryAbs(bData, inStream);
+ break;
+ case SUBPARAM_DEFERRED_DELIVERY_TIME_RELATIVE:
+ decodeSuccess = decodeDeferredDeliveryRel(bData, inStream);
+ break;
+ case SUBPARAM_PRIVACY_INDICATOR:
+ decodeSuccess = decodePrivacyIndicator(bData, inStream);
+ break;
+ case SUBPARAM_LANGUAGE_INDICATOR:
+ decodeSuccess = decodeLanguageIndicator(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_DISPLAY_MODE:
+ decodeSuccess = decodeDisplayMode(bData, inStream);
+ break;
+ case SUBPARAM_PRIORITY_INDICATOR:
+ decodeSuccess = decodePriorityIndicator(bData, inStream);
+ break;
+ case SUBPARAM_ALERT_ON_MESSAGE_DELIVERY:
+ decodeSuccess = decodeMsgDeliveryAlert(bData, inStream);
+ break;
+ case SUBPARAM_MESSAGE_DEPOSIT_INDEX:
+ decodeSuccess = decodeDepositIndex(bData, inStream);
+ break;
+ case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA:
+ decodeSuccess = decodeServiceCategoryProgramData(bData, inStream);
+ break;
+ default:
+ decodeSuccess = decodeReserved(bData, inStream, subparamId);
+ }
+ if (decodeSuccess &&
+ (subparamId >= SUBPARAM_MESSAGE_IDENTIFIER &&
+ subparamId <= SUBPARAM_ID_LAST_DEFINED)) {
+ foundSubparamMask |= subparamIdBit;
+ }
+ }
+ if ((foundSubparamMask & (1 << SUBPARAM_MESSAGE_IDENTIFIER)) == 0) {
+ throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
+ }
+ if (bData.userData != null) {
+ if (isCmasAlertCategory(serviceCategory)) {
+ decodeCmasUserData(bData, serviceCategory);
+ } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
+ if ((foundSubparamMask ^
+ (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
+ (1 << SUBPARAM_USER_DATA))
+ != 0) {
+ Rlog.e(LOG_TAG, "IS-91 must occur without extra subparams (" +
+ foundSubparamMask + ")");
+ }
+ decodeIs91(bData);
+ } else {
+ decodeUserDataPayload(bData.userData, bData.hasUserDataHeader);
+ }
+ }
+ return bData;
+ } catch (BitwiseInputStream.AccessException ex) {
+ Rlog.e(LOG_TAG, "BearerData decode failed: " + ex);
+ } catch (CodingException ex) {
+ Rlog.e(LOG_TAG, "BearerData decode failed: " + ex);
+ }
+ return null;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
new file mode 100644
index 0000000..5f2e561
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsAddress.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2008 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.sms;
+
+import android.util.SparseBooleanArray;
+
+import com.android.internal.telephony.SmsAddress;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.HexDump;
+
+public class CdmaSmsAddress extends SmsAddress {
+
+ /**
+ * Digit Mode Indicator is a 1-bit value that indicates whether
+ * the address digits are 4-bit DTMF codes or 8-bit codes. (See
+ * 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ static public final int DIGIT_MODE_4BIT_DTMF = 0x00;
+ static public final int DIGIT_MODE_8BIT_CHAR = 0x01;
+
+ public int digitMode;
+
+ /**
+ * Number Mode Indicator is 1-bit value that indicates whether the
+ * address type is a data network address or not. (See 3GPP2
+ * C.S0015-B, v2, 3.4.3.3)
+ */
+ static public final int NUMBER_MODE_NOT_DATA_NETWORK = 0x00;
+ static public final int NUMBER_MODE_DATA_NETWORK = 0x01;
+
+ public int numberMode;
+
+ /**
+ * Number Types for data networks.
+ * (See 3GPP2 C.S005-D, table2.7.1.3.2.4-2 for complete table)
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3 for data network subset)
+ * NOTE: value is stored in the parent class ton field.
+ */
+ static public final int TON_UNKNOWN = 0x00;
+ static public final int TON_INTERNATIONAL_OR_IP = 0x01;
+ static public final int TON_NATIONAL_OR_EMAIL = 0x02;
+ static public final int TON_NETWORK = 0x03;
+ static public final int TON_SUBSCRIBER = 0x04;
+ static public final int TON_ALPHANUMERIC = 0x05;
+ static public final int TON_ABBREVIATED = 0x06;
+ static public final int TON_RESERVED = 0x07;
+
+ /**
+ * Maximum lengths for fields as defined in ril_cdma_sms.h.
+ */
+ static public final int SMS_ADDRESS_MAX = 36;
+ static public final int SMS_SUBADDRESS_MAX = 36;
+
+ /**
+ * This field shall be set to the number of address digits
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public int numberOfDigits;
+
+ /**
+ * Numbering Plan identification is a 0 or 4-bit value that
+ * indicates which numbering plan identification is set. (See
+ * 3GPP2, C.S0015-B, v2, 3.4.3.3 and C.S005-D, table2.7.1.3.2.4-3)
+ */
+ static public final int NUMBERING_PLAN_UNKNOWN = 0x0;
+ static public final int NUMBERING_PLAN_ISDN_TELEPHONY = 0x1;
+ //static protected final int NUMBERING_PLAN_DATA = 0x3;
+ //static protected final int NUMBERING_PLAN_TELEX = 0x4;
+ //static protected final int NUMBERING_PLAN_PRIVATE = 0x9;
+
+ public int numberPlan;
+
+ /**
+ * NOTE: the parsed string address and the raw byte array values
+ * are stored in the parent class address and origBytes fields,
+ * respectively.
+ */
+
+ public CdmaSmsAddress(){
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CdmaSmsAddress ");
+ builder.append("{ digitMode=" + digitMode);
+ builder.append(", numberMode=" + numberMode);
+ builder.append(", numberPlan=" + numberPlan);
+ builder.append(", numberOfDigits=" + numberOfDigits);
+ builder.append(", ton=" + ton);
+ builder.append(", address=\"" + address + "\"");
+ builder.append(", origBytes=" + HexDump.toHexString(origBytes));
+ builder.append(" }");
+ return builder.toString();
+ }
+
+ /*
+ * TODO(cleanup): Refactor the parsing for addresses to better
+ * share code and logic with GSM. Also, gather all DTMF/BCD
+ * processing code in one place.
+ */
+
+ private static byte[] parseToDtmf(String address) {
+ int digits = address.length();
+ byte[] result = new byte[digits];
+ for (int i = 0; i < digits; i++) {
+ char c = address.charAt(i);
+ int val = 0;
+ if ((c >= '1') && (c <= '9')) val = c - '0';
+ else if (c == '0') val = 10;
+ else if (c == '*') val = 11;
+ else if (c == '#') val = 12;
+ else return null;
+ result[i] = (byte)val;
+ }
+ return result;
+ }
+
+ private static final char[] numericCharsDialable = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#'
+ };
+
+ private static final char[] numericCharsSugar = {
+ '(', ')', ' ', '-', '+', '.', '/', '\\'
+ };
+
+ private static final SparseBooleanArray numericCharDialableMap = new SparseBooleanArray (
+ numericCharsDialable.length + numericCharsSugar.length);
+ static {
+ for (int i = 0; i < numericCharsDialable.length; i++) {
+ numericCharDialableMap.put(numericCharsDialable[i], true);
+ }
+ for (int i = 0; i < numericCharsSugar.length; i++) {
+ numericCharDialableMap.put(numericCharsSugar[i], false);
+ }
+ }
+
+ /**
+ * Given a numeric address string, return the string without
+ * syntactic sugar, meaning parens, spaces, hyphens/minuses, or
+ * plus signs. If the input string contains non-numeric
+ * non-punctuation characters, return null.
+ */
+ private static String filterNumericSugar(String address) {
+ StringBuilder builder = new StringBuilder();
+ int len = address.length();
+ for (int i = 0; i < len; i++) {
+ char c = address.charAt(i);
+ int mapIndex = numericCharDialableMap.indexOfKey(c);
+ if (mapIndex < 0) return null;
+ if (! numericCharDialableMap.valueAt(mapIndex)) continue;
+ builder.append(c);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Given a string, return the string without whitespace,
+ * including CR/LF.
+ */
+ private static String filterWhitespace(String address) {
+ StringBuilder builder = new StringBuilder();
+ int len = address.length();
+ for (int i = 0; i < len; i++) {
+ char c = address.charAt(i);
+ if ((c == ' ') || (c == '\r') || (c == '\n') || (c == '\t')) continue;
+ builder.append(c);
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Given a string, create a corresponding CdmaSmsAddress object.
+ *
+ * The result will be null if the input string is not
+ * representable using printable ASCII.
+ *
+ * For numeric addresses, the string is cleaned up by removing
+ * common punctuation. For alpha addresses, the string is cleaned
+ * up by removing whitespace.
+ */
+ public static CdmaSmsAddress parse(String address) {
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ addr.address = address;
+ addr.ton = CdmaSmsAddress.TON_UNKNOWN;
+ byte[] origBytes = null;
+ String filteredAddr = filterNumericSugar(address);
+ if (filteredAddr != null) {
+ origBytes = parseToDtmf(filteredAddr);
+ }
+ if (origBytes != null) {
+ addr.digitMode = DIGIT_MODE_4BIT_DTMF;
+ addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+ if (address.indexOf('+') != -1) {
+ addr.ton = TON_INTERNATIONAL_OR_IP;
+ }
+ } else {
+ filteredAddr = filterWhitespace(address);
+ origBytes = UserData.stringToAscii(filteredAddr);
+ if (origBytes == null) {
+ return null;
+ }
+ addr.digitMode = DIGIT_MODE_8BIT_CHAR;
+ addr.numberMode = NUMBER_MODE_DATA_NETWORK;
+ if (address.indexOf('@') != -1) {
+ addr.ton = TON_NATIONAL_OR_EMAIL;
+ }
+ }
+ addr.origBytes = origBytes;
+ addr.numberOfDigits = origBytes.length;
+ return addr;
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java
new file mode 100644
index 0000000..0d5b502
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSmsSubaddress.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project. All rights reserved.
+ *
+ * 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.sms;
+
+public class CdmaSmsSubaddress {
+ public int type;
+
+ public byte odd;
+
+ public byte[] origBytes;
+}
+
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java
new file mode 100644
index 0000000..f73df56
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsEnvelope.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2008 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.sms;
+
+
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+
+public final class SmsEnvelope {
+ /**
+ * Message Types
+ * (See 3GPP2 C.S0015-B 3.4.1)
+ */
+ static public final int MESSAGE_TYPE_POINT_TO_POINT = 0x00;
+ static public final int MESSAGE_TYPE_BROADCAST = 0x01;
+ static public final int MESSAGE_TYPE_ACKNOWLEDGE = 0x02;
+
+ /**
+ * Supported Teleservices
+ * (See 3GPP2 N.S0005 and TIA-41)
+ */
+ static public final int TELESERVICE_NOT_SET = 0x0000;
+ static public final int TELESERVICE_WMT = 0x1002;
+ 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
+ */
+ // Voice mail notification through Message Waiting Indication in CDMA mode or Analog mode.
+ // Defined in 3GPP2 C.S-0005, 3.7.5.6, an Info Record containing an 8-bit number with the
+ // number of messages waiting, it's used by some CDMA carriers for a voice mail count.
+ static public final int TELESERVICE_MWI = 0x40000;
+
+ // Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
+ // static final int SERVICE_CATEGORY_EMERGENCY = 0x0001;
+ //...
+
+ // 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
+ */
+ public int messageType;
+
+ /**
+ * The 16-bit Teleservice parameter identifies which upper layer service access point is sending
+ * or receiving the message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.1)
+ */
+ public int teleService = TELESERVICE_NOT_SET;
+
+ /**
+ * The 16-bit service category parameter identifies the type of service provided
+ * by the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.2)
+ */
+ public int serviceCategory;
+
+ /**
+ * The origination address identifies the originator of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public CdmaSmsAddress origAddress;
+
+ /**
+ * The destination address identifies the target of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.3)
+ */
+ public CdmaSmsAddress destAddress;
+
+ /**
+ * The origination subaddress identifies the originator of the SMS message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.4)
+ */
+ public CdmaSmsSubaddress origSubaddress;
+
+ /**
+ * The 6-bit bearer reply parameter is used to request the return of a
+ * SMS Acknowledge Message.
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.5)
+ */
+ public int bearerReply;
+
+ /**
+ * Cause Code values:
+ * The cause code parameters are an indication whether an SMS error has occurred and if so,
+ * whether the condition is considered temporary or permanent.
+ * ReplySeqNo 6-bit value,
+ * ErrorClass 2-bit value,
+ * CauseCode 0-bit or 8-bit value
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.6)
+ */
+ public byte replySeqNo;
+ public byte errorClass;
+ public byte causeCode;
+
+ /**
+ * encoded bearer data
+ * (See 3GPP2 C.S0015-B, v2, 3.4.3.7)
+ */
+ public byte[] bearerData;
+
+ public SmsEnvelope() {
+ // nothing to see here
+ }
+
+}
+
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
new file mode 100644
index 0000000..629173d
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -0,0 +1,968 @@
+/*
+ * Copyright (C) 2008 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.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.telephony.Rlog;
+import android.util.Log;
+import android.text.TextUtils;
+import android.content.res.Resources;
+
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.SmsAddress;
+import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.CdmaSmsSubaddress;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.util.BitwiseInputStream;
+import com.android.internal.util.HexDump;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * TODO(cleanup): these constants are disturbing... are they not just
+ * different interpretations on one number? And if we did not have
+ * terrible class name overlap, they would not need to be directly
+ * imported like this. The class in this file could just as well be
+ * named CdmaSmsMessage, could it not?
+ */
+
+/**
+ * TODO(cleanup): internally returning null in many places makes
+ * debugging very hard (among many other reasons) and should be made
+ * more meaningful (replaced with exceptions for example). Null
+ * returns should only occur at the very outside of the module/class
+ * scope.
+ */
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage extends SmsMessageBase {
+ static final String LOG_TAG = "SmsMessage";
+ static private final String LOGGABLE_TAG = "CDMA:SMS";
+ private static final boolean VDBG = false;
+
+ private final static byte TELESERVICE_IDENTIFIER = 0x00;
+ private final static byte SERVICE_CATEGORY = 0x01;
+ private final static byte ORIGINATING_ADDRESS = 0x02;
+ private final static byte ORIGINATING_SUB_ADDRESS = 0x03;
+ private final static byte DESTINATION_ADDRESS = 0x04;
+ private final static byte DESTINATION_SUB_ADDRESS = 0x05;
+ private final static byte BEARER_REPLY_OPTION = 0x06;
+ private final static byte CAUSE_CODES = 0x07;
+ private final static byte BEARER_DATA = 0x08;
+
+ /**
+ * Status of a previously submitted SMS.
+ * This field applies to SMS Delivery Acknowledge messages. 0 indicates success;
+ * Here, the error class is defined by the bits from 9-8, the status code by the bits from 7-0.
+ * See C.S0015-B, v2.0, 4.5.21 for a detailed description of possible values.
+ */
+ private int status;
+
+ /** Specifies if a return of an acknowledgment is requested for send SMS */
+ private static final int RETURN_NO_ACK = 0;
+ private static final int RETURN_ACK = 1;
+
+ private SmsEnvelope mEnvelope;
+ private BearerData mBearerData;
+
+ /** @hide */
+ public SmsMessage(SmsAddress addr, SmsEnvelope env) {
+ mOriginatingAddress = addr;
+ mEnvelope = env;
+ createPdu();
+ }
+
+ public SmsMessage() {}
+
+ public static class SubmitPdu extends SubmitPduBase {
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU.
+ * Note: In CDMA the PDU is just a byte representation of the received Sms.
+ */
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ SmsMessage msg = new SmsMessage();
+
+ try {
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ } catch (OutOfMemoryError e) {
+ Log.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
+ return null;
+ }
+ }
+
+ /**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by RuimSmsInterfaceManager.getAllMessagesFromIcc + 1.
+ * @param data Record data.
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data) {
+ try {
+ SmsMessage msg = new SmsMessage();
+
+ msg.mIndexOnIcc = index;
+
+ // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
+ // or STORED_UNSENT
+ // See 3GPP2 C.S0023 3.4.27
+ if ((data[0] & 1) == 0) {
+ Rlog.w(LOG_TAG, "SMS parsing failed: Trying to parse a free record");
+ return null;
+ } else {
+ msg.mStatusOnIcc = data[0] & 0x07;
+ }
+
+ // Second byte is the MSG_LEN, length of the message
+ // See 3GPP2 C.S0023 3.4.27
+ int size = data[1];
+
+ // Note: Data may include trailing FF's. That's OK; message
+ // should still parse correctly.
+ byte[] pdu = new byte[size];
+ System.arraycopy(data, 2, pdu, 0, size);
+ // the message has to be parsed before it can be displayed
+ // see gsm.SmsMessage
+ msg.parsePduFromEfRecord(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ Rlog.w(LOG_TAG, "getTPLayerLengthForPDU: is not supported in CDMA mode.");
+ return 0;
+ }
+
+ /**
+ * TODO(cleanup): why do getSubmitPdu methods take an scAddr input
+ * and do nothing with it? GSM allows us to specify a SC (eg,
+ * when responding to an SMS that explicitly requests the response
+ * is sent to a specific SC), or pass null to use the default
+ * value. Is there no similar notion in CDMA? Or do we just not
+ * have it hooked up?
+ */
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddr Service Centre address. Null means use default.
+ * @param destAddr Address of the recipient.
+ * @param message String representation of the message payload.
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @param smsHeader Array containing the data for the User Data Header, preceded
+ * by the Element Identifiers.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, String message,
+ boolean statusReportRequested, SmsHeader smsHeader) {
+
+ /**
+ * TODO(cleanup): Do we really want silent failure like this?
+ * Would it not be much more reasonable to make sure we don't
+ * call this function if we really want nothing done?
+ */
+ if (message == null || destAddr == null) {
+ return null;
+ }
+
+ UserData uData = new UserData();
+ uData.payloadStr = message;
+ uData.userDataHeader = smsHeader;
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address and port.
+ *
+ * @param scAddr Service Centre address. null == use default
+ * @param destAddr the address of the destination for the message
+ * @param destPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddr, String destAddr, int destPort,
+ byte[] data, boolean statusReportRequested) {
+
+ /**
+ * TODO(cleanup): this is not a general-purpose SMS creation
+ * method, but rather something specialized to messages
+ * containing OCTET encoded (meaning non-human-readable) user
+ * data. The name should reflect that, and not just overload.
+ */
+
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = destPort;
+ portAddrs.origPort = 0;
+ portAddrs.areEightBits = false;
+
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.portAddrs = portAddrs;
+
+ UserData uData = new UserData();
+ uData.userDataHeader = smsHeader;
+ uData.msgEncoding = UserData.ENCODING_OCTET;
+ uData.msgEncodingSet = true;
+ uData.payload = data;
+
+ return privateGetSubmitPdu(destAddr, statusReportRequested, uData);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port
+ *
+ * @param destAddr the address of the destination for the message
+ * @param userData the data for the message
+ * @param statusReportRequested Indicates whether a report is requested for this message.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String destAddr, UserData userData,
+ boolean statusReportRequested) {
+ return privateGetSubmitPdu(destAddr, statusReportRequested, userData);
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public int getProtocolIdentifier() {
+ Rlog.w(LOG_TAG, "getProtocolIdentifier: is not supported in CDMA mode.");
+ // (3GPP TS 23.040): "no interworking, but SME to SME protocol":
+ return 0;
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isReplace() {
+ Rlog.w(LOG_TAG, "isReplace: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isCphsMwiMessage() {
+ Rlog.w(LOG_TAG, "isCphsMwiMessage: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMWIClearMessage() {
+ return ((mBearerData != null) && (mBearerData.numberOfMessages == 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMWISetMessage() {
+ return ((mBearerData != null) && (mBearerData.numberOfMessages > 0));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isMwiDontStore() {
+ return ((mBearerData != null) &&
+ (mBearerData.numberOfMessages > 0) &&
+ (mBearerData.userData == null));
+ }
+
+ /**
+ * Returns the status for a previously submitted message.
+ * For not interfering with status codes from GSM, this status code is
+ * shifted to the bits 31-16.
+ */
+ @Override
+ public int getStatus() {
+ return (status << 16);
+ }
+
+ /** Return true iff the bearer data message type is DELIVERY_ACK. */
+ @Override
+ public boolean isStatusReportMessage() {
+ return (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK);
+ }
+
+ /**
+ * Note: This function is a GSM specific functionality which is not supported in CDMA mode.
+ */
+ @Override
+ public boolean isReplyPathPresent() {
+ Rlog.w(LOG_TAG, "isReplyPathPresent: is not supported in CDMA mode.");
+ return false;
+ }
+
+ /**
+ * Calculate the number of septets needed to encode the message.
+ *
+ * @param messageBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @param isEntireMsg indicates if this is entire msg or a segment in multipart msg
+ * @return TextEncodingDetails
+ */
+ public static TextEncodingDetails calculateLength(CharSequence messageBody,
+ boolean use7bitOnly, boolean isEntireMsg) {
+ CharSequence newMsgBody = null;
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+ newMsgBody = Sms7BitEncodingTranslator.translate(messageBody);
+ }
+ if (TextUtils.isEmpty(newMsgBody)) {
+ newMsgBody = messageBody;
+ }
+ return BearerData.calcTextEncodingDetails(newMsgBody, use7bitOnly, isEntireMsg);
+ }
+
+ /**
+ * Returns the teleservice type of the message.
+ * @return the teleservice:
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WMT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WEMT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_VMN},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_WAP}
+ */
+ public int getTeleService() {
+ return mEnvelope.teleService;
+ }
+
+ /**
+ * Returns the message type of the message.
+ * @return the message type:
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_POINT_TO_POINT},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_BROADCAST},
+ * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#MESSAGE_TYPE_ACKNOWLEDGE},
+ */
+ public int getMessageType() {
+ // NOTE: mEnvelope.messageType is not set correctly for cell broadcasts with some RILs.
+ // Use the service category parameter to detect CMAS and other cell broadcast messages.
+ if (mEnvelope.serviceCategory != 0) {
+ return SmsEnvelope.MESSAGE_TYPE_BROADCAST;
+ } else {
+ return SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+ }
+ }
+
+ /**
+ * Decodes pdu to an empty SMS object.
+ * In the CDMA case the pdu is just an internal byte stream representation
+ * of the SMS Java-object.
+ * @see #createPdu()
+ */
+ private void parsePdu(byte[] pdu) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+ int length;
+ int bearerDataLength;
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+
+ try {
+ env.messageType = dis.readInt();
+ env.teleService = dis.readInt();
+ env.serviceCategory = dis.readInt();
+
+ addr.digitMode = dis.readByte();
+ addr.numberMode = dis.readByte();
+ addr.ton = dis.readByte();
+ addr.numberPlan = dis.readByte();
+
+ length = dis.readUnsignedByte();
+ addr.numberOfDigits = length;
+
+ // sanity check on the length
+ if (length > pdu.length) {
+ throw new RuntimeException(
+ "createFromPdu: Invalid pdu, addr.numberOfDigits " + length
+ + " > pdu len " + pdu.length);
+ }
+ addr.origBytes = new byte[length];
+ dis.read(addr.origBytes, 0, length); // digits
+
+ env.bearerReply = dis.readInt();
+ // CauseCode values:
+ env.replySeqNo = dis.readByte();
+ env.errorClass = dis.readByte();
+ env.causeCode = dis.readByte();
+
+ //encoded BearerData:
+ bearerDataLength = dis.readInt();
+ // sanity check on the length
+ if (bearerDataLength > pdu.length) {
+ throw new RuntimeException(
+ "createFromPdu: Invalid pdu, bearerDataLength " + bearerDataLength
+ + " > pdu len " + pdu.length);
+ }
+ env.bearerData = new byte[bearerDataLength];
+ dis.read(env.bearerData, 0, bearerDataLength);
+ dis.close();
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "createFromPdu: conversion from byte array to object failed: " + ex, ex);
+ } catch (Exception ex) {
+ Rlog.e(LOG_TAG, "createFromPdu: conversion from byte array to object failed: " + ex);
+ }
+
+ // link the filled objects to this SMS
+ mOriginatingAddress = addr;
+ env.origAddress = addr;
+ mEnvelope = env;
+ mPdu = pdu;
+
+ parseSms();
+ }
+
+ /**
+ * Decodes 3GPP2 sms stored in CSIM/RUIM cards As per 3GPP2 C.S0015-0
+ */
+ private void parsePduFromEfRecord(byte[] pdu) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(pdu);
+ DataInputStream dis = new DataInputStream(bais);
+ SmsEnvelope env = new SmsEnvelope();
+ CdmaSmsAddress addr = new CdmaSmsAddress();
+ CdmaSmsSubaddress subAddr = new CdmaSmsSubaddress();
+
+ try {
+ env.messageType = dis.readByte();
+
+ while (dis.available() > 0) {
+ int parameterId = dis.readByte();
+ int parameterLen = dis.readUnsignedByte();
+ byte[] parameterData = new byte[parameterLen];
+
+ switch (parameterId) {
+ case TELESERVICE_IDENTIFIER:
+ /*
+ * 16 bit parameter that identifies which upper layer
+ * service access point is sending or should receive
+ * this message
+ */
+ env.teleService = dis.readUnsignedShort();
+ Rlog.i(LOG_TAG, "teleservice = " + env.teleService);
+ break;
+ case SERVICE_CATEGORY:
+ /*
+ * 16 bit parameter that identifies type of service as
+ * in 3GPP2 C.S0015-0 Table 3.4.3.2-1
+ */
+ env.serviceCategory = dis.readUnsignedShort();
+ break;
+ case ORIGINATING_ADDRESS:
+ case DESTINATION_ADDRESS:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream addrBis = new BitwiseInputStream(parameterData);
+ addr.digitMode = addrBis.read(1);
+ addr.numberMode = addrBis.read(1);
+ int numberType = 0;
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ numberType = addrBis.read(3);
+ addr.ton = numberType;
+
+ if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK)
+ addr.numberPlan = addrBis.read(4);
+ }
+
+ addr.numberOfDigits = addrBis.read(8);
+
+ byte[] data = new byte[addr.numberOfDigits];
+ byte b = 0x00;
+
+ if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF) {
+ /* As per 3GPP2 C.S0005-0 Table 2.7.1.3.2.4-4 */
+ for (int index = 0; index < addr.numberOfDigits; index++) {
+ b = (byte) (0xF & addrBis.read(4));
+ // convert the value if it is 4-bit DTMF to 8
+ // bit
+ data[index] = convertDtmfToAscii(b);
+ }
+ } else if (addr.digitMode == CdmaSmsAddress.DIGIT_MODE_8BIT_CHAR) {
+ if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK) {
+ for (int index = 0; index < addr.numberOfDigits; index++) {
+ b = (byte) (0xFF & addrBis.read(8));
+ data[index] = b;
+ }
+
+ } else if (addr.numberMode == CdmaSmsAddress.NUMBER_MODE_DATA_NETWORK) {
+ if (numberType == 2)
+ Rlog.e(LOG_TAG, "TODO: Originating Addr is email id");
+ else
+ Rlog.e(LOG_TAG,
+ "TODO: Originating Addr is data network address");
+ } else {
+ Rlog.e(LOG_TAG, "Originating Addr is of incorrect type");
+ }
+ } else {
+ Rlog.e(LOG_TAG, "Incorrect Digit mode");
+ }
+ addr.origBytes = data;
+ Rlog.i(LOG_TAG, "Originating Addr=" + addr.toString());
+ break;
+ case ORIGINATING_SUB_ADDRESS:
+ case DESTINATION_SUB_ADDRESS:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream subAddrBis = new BitwiseInputStream(parameterData);
+ subAddr.type = subAddrBis.read(3);
+ subAddr.odd = subAddrBis.readByteArray(1)[0];
+ int subAddrLen = subAddrBis.read(8);
+ byte[] subdata = new byte[subAddrLen];
+ for (int index = 0; index < subAddrLen; index++) {
+ b = (byte) (0xFF & subAddrBis.read(4));
+ // convert the value if it is 4-bit DTMF to 8 bit
+ subdata[index] = convertDtmfToAscii(b);
+ }
+ subAddr.origBytes = subdata;
+ break;
+ case BEARER_REPLY_OPTION:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream replyOptBis = new BitwiseInputStream(parameterData);
+ env.bearerReply = replyOptBis.read(6);
+ break;
+ case CAUSE_CODES:
+ dis.read(parameterData, 0, parameterLen);
+ BitwiseInputStream ccBis = new BitwiseInputStream(parameterData);
+ env.replySeqNo = ccBis.readByteArray(6)[0];
+ env.errorClass = ccBis.readByteArray(2)[0];
+ if (env.errorClass != 0x00)
+ env.causeCode = ccBis.readByteArray(8)[0];
+ break;
+ case BEARER_DATA:
+ dis.read(parameterData, 0, parameterLen);
+ env.bearerData = parameterData;
+ break;
+ default:
+ throw new Exception("unsupported parameterId (" + parameterId + ")");
+ }
+ }
+ bais.close();
+ dis.close();
+ } catch (Exception ex) {
+ Rlog.e(LOG_TAG, "parsePduFromEfRecord: conversion from pdu to SmsMessage failed" + ex);
+ }
+
+ // link the filled objects to this SMS
+ mOriginatingAddress = addr;
+ env.origAddress = addr;
+ env.origSubaddress = subAddr;
+ mEnvelope = env;
+ mPdu = pdu;
+
+ parseSms();
+ }
+
+ /**
+ * Parses a SMS message from its BearerData stream. (mobile-terminated only)
+ */
+ public void parseSms() {
+ // Message Waiting Info Record defined in 3GPP2 C.S-0005, 3.7.5.6
+ // It contains only an 8-bit number with the number of messages waiting
+ if (mEnvelope.teleService == SmsEnvelope.TELESERVICE_MWI) {
+ mBearerData = new BearerData();
+ if (mEnvelope.bearerData != null) {
+ mBearerData.numberOfMessages = 0x000000FF & mEnvelope.bearerData[0];
+ }
+ if (VDBG) {
+ Rlog.d(LOG_TAG, "parseSms: get MWI " +
+ Integer.toString(mBearerData.numberOfMessages));
+ }
+ return;
+ }
+ mBearerData = BearerData.decode(mEnvelope.bearerData);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MT raw BearerData = '" +
+ HexDump.toHexString(mEnvelope.bearerData) + "'");
+ Rlog.d(LOG_TAG, "MT (decoded) BearerData = " + mBearerData);
+ }
+ mMessageRef = mBearerData.messageId;
+ if (mBearerData.userData != null) {
+ mUserData = mBearerData.userData.payload;
+ mUserDataHeader = mBearerData.userData.userDataHeader;
+ mMessageBody = mBearerData.userData.payloadStr;
+ }
+
+ if (mOriginatingAddress != null) {
+ mOriginatingAddress.address = new String(mOriginatingAddress.origBytes);
+ if (mOriginatingAddress.ton == CdmaSmsAddress.TON_INTERNATIONAL_OR_IP) {
+ if (mOriginatingAddress.address.charAt(0) != '+') {
+ mOriginatingAddress.address = "+" + mOriginatingAddress.address;
+ }
+ }
+ if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+ + mOriginatingAddress.address);
+ }
+
+ if (mBearerData.msgCenterTimeStamp != null) {
+ mScTimeMillis = mBearerData.msgCenterTimeStamp.toMillis(true);
+ }
+
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
+
+ // Message Type (See 3GPP2 C.S0015-B, v2, 4.5.1)
+ if (mBearerData.messageType == BearerData.MESSAGE_TYPE_DELIVERY_ACK) {
+ // The BearerData MsgStatus subparameter should only be
+ // included for DELIVERY_ACK messages. If it occurred for
+ // other messages, it would be unclear what the status
+ // being reported refers to. The MsgStatus subparameter
+ // is primarily useful to indicate error conditions -- a
+ // message without this subparameter is assumed to
+ // indicate successful delivery (status == 0).
+ if (! mBearerData.messageStatusSet) {
+ Rlog.d(LOG_TAG, "DELIVERY_ACK message without msgStatus (" +
+ (mUserData == null ? "also missing" : "does have") +
+ " userData).");
+ status = 0;
+ } else {
+ status = mBearerData.errorClass << 8;
+ status |= mBearerData.messageStatus;
+ }
+ } else if (mBearerData.messageType != BearerData.MESSAGE_TYPE_DELIVER) {
+ throw new RuntimeException("Unsupported message type: " + mBearerData.messageType);
+ }
+
+ if (mMessageBody != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS message body: '" + mMessageBody + "'");
+ parseMessageBody();
+ } else if ((mUserData != null) && VDBG) {
+ Rlog.v(LOG_TAG, "SMS payload: '" + IccUtils.bytesToHexString(mUserData) + "'");
+ }
+ }
+
+ /**
+ * Parses a broadcast SMS, possibly containing a CMAS alert.
+ */
+ public SmsCbMessage parseBroadcastSms() {
+ BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
+ if (bData == null) {
+ Rlog.w(LOG_TAG, "BearerData.decode() returned null");
+ return null;
+ }
+
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
+ }
+
+ String plmn = TelephonyManager.getDefault().getNetworkOperator();
+ 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}
+ */
+ @Override
+ public SmsConstants.MessageClass getMessageClass() {
+ if (BearerData.DISPLAY_MODE_IMMEDIATE == mBearerData.displayMode ) {
+ return SmsConstants.MessageClass.CLASS_0;
+ } else {
+ return SmsConstants.MessageClass.UNKNOWN;
+ }
+ }
+
+ /**
+ * Calculate the next message id, starting at 1 and iteratively
+ * incrementing within the range 1..65535 remembering the state
+ * via a persistent system property. (See C.S0015-B, v2.0,
+ * 4.3.1.5) Since this routine is expected to be accessed via via
+ * binder-call, and hence should be thread-safe, it has been
+ * synchronized.
+ */
+ public synchronized static int getNextMessageId() {
+ // Testing and dialog with partners has indicated that
+ // msgId==0 is (sometimes?) treated specially by lower levels.
+ // Specifically, the ID is not preserved for delivery ACKs.
+ // Hence, avoid 0 -- constraining the range to 1..65535.
+ int msgId = SystemProperties.getInt(TelephonyProperties.PROPERTY_CDMA_MSG_ID, 1);
+ String nextMsgId = Integer.toString((msgId % 0xFFFF) + 1);
+ try{
+ SystemProperties.set(TelephonyProperties.PROPERTY_CDMA_MSG_ID, nextMsgId);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "next " + TelephonyProperties.PROPERTY_CDMA_MSG_ID + " = " + nextMsgId);
+ Rlog.d(LOG_TAG, "readback gets " +
+ SystemProperties.get(TelephonyProperties.PROPERTY_CDMA_MSG_ID));
+ }
+ } catch(RuntimeException ex) {
+ Rlog.e(LOG_TAG, "set nextMessage ID failed: " + ex);
+ }
+ return msgId;
+ }
+
+ /**
+ * Creates BearerData and Envelope from parameters for a Submit SMS.
+ * @return byte stream for SubmitPdu.
+ */
+ private static SubmitPdu privateGetSubmitPdu(String destAddrStr, boolean statusReportRequested,
+ UserData userData) {
+
+ /**
+ * TODO(cleanup): give this function a more meaningful name.
+ */
+
+ /**
+ * TODO(cleanup): Make returning null from the getSubmitPdu
+ * variations meaningful -- clean up the error feedback
+ * mechanism, and avoid null pointer exceptions.
+ */
+
+ /**
+ * North America Plus Code :
+ * Convert + code to 011 and dial out for international SMS
+ */
+ CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr));
+ if (destAddr == null) return null;
+
+ BearerData bearerData = new BearerData();
+ bearerData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
+
+ bearerData.messageId = getNextMessageId();
+
+ bearerData.deliveryAckReq = statusReportRequested;
+ bearerData.userAckReq = false;
+ bearerData.readAckReq = false;
+ bearerData.reportReq = false;
+
+ bearerData.userData = userData;
+
+ byte[] encodedBearerData = BearerData.encode(bearerData);
+ if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+ Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
+ Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
+ }
+ if (encodedBearerData == null) return null;
+
+ int teleservice = bearerData.hasUserDataHeader ?
+ SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
+
+ SmsEnvelope envelope = new SmsEnvelope();
+ envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT;
+ envelope.teleService = teleservice;
+ envelope.destAddress = destAddr;
+ envelope.bearerReply = RETURN_ACK;
+ envelope.bearerData = encodedBearerData;
+
+ /**
+ * TODO(cleanup): envelope looks to be a pointless class, get
+ * rid of it. Also -- most of the envelope fields set here
+ * are ignored, why?
+ */
+
+ try {
+ /**
+ * TODO(cleanup): reference a spec and get rid of the ugly comments
+ */
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.writeInt(envelope.teleService);
+ dos.writeInt(0); //servicePresent
+ dos.writeInt(0); //serviceCategory
+ dos.write(destAddr.digitMode);
+ dos.write(destAddr.numberMode);
+ dos.write(destAddr.ton); // number_type
+ dos.write(destAddr.numberPlan);
+ dos.write(destAddr.numberOfDigits);
+ dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+ // Subaddress is not supported.
+ dos.write(0); //subaddressType
+ dos.write(0); //subaddr_odd
+ dos.write(0); //subaddr_nbr_of_digits
+ dos.write(encodedBearerData.length);
+ dos.write(encodedBearerData, 0, encodedBearerData.length);
+ dos.close();
+
+ SubmitPdu pdu = new SubmitPdu();
+ pdu.encodedMessage = baos.toByteArray();
+ pdu.encodedScAddress = null;
+ return pdu;
+ } catch(IOException ex) {
+ Rlog.e(LOG_TAG, "creating SubmitPdu failed: " + ex);
+ }
+ return null;
+ }
+
+ /**
+ * Creates byte array (pseudo pdu) from SMS object.
+ * Note: Do not call this method more than once per object!
+ * @hide
+ */
+ public void createPdu() {
+ SmsEnvelope env = mEnvelope;
+ CdmaSmsAddress addr = env.origAddress;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(baos));
+
+ try {
+ dos.writeInt(env.messageType);
+ dos.writeInt(env.teleService);
+ dos.writeInt(env.serviceCategory);
+
+ dos.writeByte(addr.digitMode);
+ dos.writeByte(addr.numberMode);
+ dos.writeByte(addr.ton);
+ dos.writeByte(addr.numberPlan);
+ dos.writeByte(addr.numberOfDigits);
+ dos.write(addr.origBytes, 0, addr.origBytes.length); // digits
+
+ dos.writeInt(env.bearerReply);
+ // CauseCode values:
+ dos.writeByte(env.replySeqNo);
+ dos.writeByte(env.errorClass);
+ dos.writeByte(env.causeCode);
+ //encoded BearerData:
+ dos.writeInt(env.bearerData.length);
+ dos.write(env.bearerData, 0, env.bearerData.length);
+ dos.close();
+
+ /**
+ * TODO(cleanup) -- The mPdu field is managed in
+ * a fragile manner, and it would be much nicer if
+ * accessing the serialized representation used a less
+ * fragile mechanism. Maybe the getPdu method could
+ * generate a representation if there was not yet one?
+ */
+
+ mPdu = baos.toByteArray();
+ } catch (IOException ex) {
+ Rlog.e(LOG_TAG, "createPdu: conversion from object to byte array failed: " + ex);
+ }
+ }
+
+ /**
+ * Converts a 4-Bit DTMF encoded symbol from the calling address number to ASCII character
+ * @hide
+ */
+ public static byte convertDtmfToAscii(byte dtmfDigit) {
+ byte asciiDigit;
+
+ switch (dtmfDigit) {
+ case 0: asciiDigit = 68; break; // 'D'
+ case 1: asciiDigit = 49; break; // '1'
+ case 2: asciiDigit = 50; break; // '2'
+ case 3: asciiDigit = 51; break; // '3'
+ case 4: asciiDigit = 52; break; // '4'
+ case 5: asciiDigit = 53; break; // '5'
+ case 6: asciiDigit = 54; break; // '6'
+ case 7: asciiDigit = 55; break; // '7'
+ case 8: asciiDigit = 56; break; // '8'
+ case 9: asciiDigit = 57; break; // '9'
+ case 10: asciiDigit = 48; break; // '0'
+ case 11: asciiDigit = 42; break; // '*'
+ case 12: asciiDigit = 35; break; // '#'
+ case 13: asciiDigit = 65; break; // 'A'
+ case 14: asciiDigit = 66; break; // 'B'
+ case 15: asciiDigit = 67; break; // 'C'
+ default:
+ asciiDigit = 32; // Invalid DTMF code
+ break;
+ }
+
+ return asciiDigit;
+ }
+
+ /** This function shall be called to get the number of voicemails.
+ * @hide
+ */
+ public int getNumOfVoicemails() {
+ return mBearerData.numberOfMessages;
+ }
+
+ /**
+ * Returns a byte array that can be use to uniquely identify a received SMS message.
+ * C.S0015-B 4.3.1.6 Unique Message Identification.
+ *
+ * @return byte array uniquely identifying the message.
+ * @hide
+ */
+ public byte[] getIncomingSmsFingerprint() {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ output.write(mEnvelope.serviceCategory);
+ output.write(mEnvelope.teleService);
+ output.write(mEnvelope.origAddress.origBytes, 0, mEnvelope.origAddress.origBytes.length);
+ output.write(mEnvelope.bearerData, 0, mEnvelope.bearerData.length);
+ output.write(mEnvelope.origSubaddress.origBytes, 0,
+ mEnvelope.origSubaddress.origBytes.length);
+
+ return output.toByteArray();
+ }
+
+ /**
+ * Returns the list of service category program data, if present.
+ * @return a list of CdmaSmsCbProgramData objects, or null if not present
+ * @hide
+ */
+ public ArrayList<CdmaSmsCbProgramData> getSmsCbProgramData() {
+ return mBearerData.serviceCategoryProgramData;
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/UserData.java b/telephony/java/com/android/internal/telephony/cdma/UserData.java
new file mode 100644
index 0000000..f879560
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/UserData.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2008 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.sms;
+
+import android.util.SparseIntArray;
+
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.util.HexDump;
+
+public class UserData {
+
+ /**
+ * User data encoding types.
+ * (See 3GPP2 C.R1001-F, v1.0, table 9.1-1)
+ */
+ public static final int ENCODING_OCTET = 0x00;
+ public static final int ENCODING_IS91_EXTENDED_PROTOCOL = 0x01;
+ 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_LATIN = 0x08;
+ public static final int ENCODING_GSM_7BIT_ALPHABET = 0x09;
+ public static final int ENCODING_GSM_DCS = 0x0A;
+
+ /**
+ * User data message type encoding types.
+ * (See 3GPP2 C.S0015-B, 4.5.2 and 3GPP 23.038, Section 4)
+ */
+ public static final int ENCODING_GSM_DCS_7BIT = 0x00;
+ public static final int ENCODING_GSM_DCS_8BIT = 0x01;
+ public static final int ENCODING_GSM_DCS_16BIT = 0x02;
+
+ /**
+ * IS-91 message types.
+ * (See TIA/EIS/IS-91-A-ENGL 1999, table 3.7.1.1-3)
+ */
+ public static final int IS91_MSG_TYPE_VOICEMAIL_STATUS = 0x82;
+ public static final int IS91_MSG_TYPE_SHORT_MESSAGE_FULL = 0x83;
+ public static final int IS91_MSG_TYPE_CLI = 0x84;
+ public static final int IS91_MSG_TYPE_SHORT_MESSAGE = 0x85;
+
+ /**
+ * US ASCII character mapping table.
+ *
+ * This table contains only the printable ASCII characters, with a
+ * 0x20 offset, meaning that the ASCII SPACE character is at index
+ * 0, with the resulting code of 0x20.
+ *
+ * Note this mapping is also equivalent to that used by both the
+ * IA5 and the IS-91 encodings. For the former this is defined
+ * using CCITT Rec. T.50 Tables 1 and 3. For the latter IS 637 B,
+ * Table 4.3.1.4.1-1 -- and note the encoding uses only 6 bits,
+ * and hence only maps entries up to the '_' character.
+ *
+ */
+ public static final char[] ASCII_MAP = {
+ ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
+ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'};
+
+ /**
+ * Character to use when forced to encode otherwise unencodable
+ * characters, meaning those not in the respective ASCII or GSM
+ * 7-bit encoding tables. Current choice is SPACE, which is 0x20
+ * in both the GSM-7bit and ASCII-7bit encodings.
+ */
+ static final byte UNENCODABLE_7_BIT_CHAR = 0x20;
+
+ /**
+ * Only elements between these indices in the ASCII table are printable.
+ */
+ public static final int PRINTABLE_ASCII_MIN_INDEX = 0x20;
+ public static final int ASCII_NL_INDEX = 0x0A;
+ public static final int ASCII_CR_INDEX = 0x0D;
+ public static final SparseIntArray charToAscii = new SparseIntArray();
+ static {
+ for (int i = 0; i < ASCII_MAP.length; i++) {
+ charToAscii.put(ASCII_MAP[i], PRINTABLE_ASCII_MIN_INDEX + i);
+ }
+ charToAscii.put('\n', ASCII_NL_INDEX);
+ charToAscii.put('\r', ASCII_CR_INDEX);
+ }
+
+ /*
+ * TODO(cleanup): Move this very generic functionality somewhere
+ * more general.
+ */
+ /**
+ * Given a string generate a corresponding ASCII-encoded byte
+ * array, but limited to printable characters. If the input
+ * contains unprintable characters, return null.
+ */
+ public static byte[] stringToAscii(String str) {
+ int len = str.length();
+ byte[] result = new byte[len];
+ for (int i = 0; i < len; i++) {
+ int charCode = charToAscii.get(str.charAt(i), -1);
+ if (charCode == -1) return null;
+ result[i] = (byte)charCode;
+ }
+ return result;
+ }
+
+ /**
+ * Mapping for ASCII values less than 32 are flow control signals
+ * and not used here.
+ */
+ public static final int ASCII_MAP_BASE_INDEX = 0x20;
+ public static final int ASCII_MAP_MAX_INDEX = ASCII_MAP_BASE_INDEX + ASCII_MAP.length - 1;
+
+ /**
+ * Contains the data header of the user data
+ */
+ public SmsHeader userDataHeader;
+
+ /**
+ * Contains the data encoding type for the SMS message
+ */
+ public int msgEncoding;
+ public boolean msgEncodingSet = false;
+
+ public int msgType;
+
+ /**
+ * Number of invalid bits in the last byte of data.
+ */
+ public int paddingBits;
+
+ public int numFields;
+
+ /**
+ * Contains the user data of a SMS message
+ * (See 3GPP2 C.S0015-B, v2, 4.5.2)
+ */
+ public byte[] payload;
+ public String payloadStr;
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("UserData ");
+ builder.append("{ msgEncoding=" + (msgEncodingSet ? msgEncoding : "unset"));
+ builder.append(", msgType=" + msgType);
+ builder.append(", paddingBits=" + paddingBits);
+ builder.append(", numFields=" + numFields);
+ builder.append(", userDataHeader=" + userDataHeader);
+ builder.append(", payload='" + HexDump.toHexString(payload) + "'");
+ builder.append(", payloadStr='" + payloadStr + "'");
+ builder.append(" }");
+ return builder.toString();
+ }
+
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/package.html b/telephony/java/com/android/internal/telephony/cdma/package.html
new file mode 100644
index 0000000..b2bc736
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/cdma/package.html
@@ -0,0 +1,6 @@
+<HTML>
+<BODY>
+Provides CDMA-specific features for text/data/PDU SMS messages
+@hide
+</BODY>
+</HTML>
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
new file mode 100644
index 0000000..2fbf7ed
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2006 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.PhoneNumberUtils;
+import java.text.ParseException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.SmsAddress;
+
+public class GsmSmsAddress extends SmsAddress {
+
+ static final int OFFSET_ADDRESS_LENGTH = 0;
+
+ static final int OFFSET_TOA = 1;
+
+ static final int OFFSET_ADDRESS_VALUE = 2;
+
+ /**
+ * New GsmSmsAddress from TS 23.040 9.1.2.5 Address Field
+ *
+ * @param offset the offset of the Address-Length byte
+ * @param length the length in bytes rounded up, e.g. "2 +
+ * (addressLength + 1) / 2"
+ * @throws ParseException
+ */
+
+ public GsmSmsAddress(byte[] data, int offset, int length) throws ParseException {
+ origBytes = new byte[length];
+ System.arraycopy(data, offset, origBytes, 0, length);
+
+ // addressLength is the count of semi-octets, not bytes
+ int addressLength = origBytes[OFFSET_ADDRESS_LENGTH] & 0xff;
+
+ int toa = origBytes[OFFSET_TOA] & 0xff;
+ ton = 0x7 & (toa >> 4);
+
+ // TOA must have its high bit set
+ if ((toa & 0x80) != 0x80) {
+ throw new ParseException("Invalid TOA - high bit must be set. toa = " + toa,
+ offset + OFFSET_TOA);
+ }
+
+ if (isAlphanumeric()) {
+ // An alphanumeric address
+ int countSeptets = addressLength * 4 / 7;
+
+ address = GsmAlphabet.gsm7BitPackedToString(origBytes,
+ OFFSET_ADDRESS_VALUE, countSeptets);
+ } else {
+ // TS 23.040 9.1.2.5 says
+ // that "the MS shall interpret reserved values as 'Unknown'
+ // but shall store them exactly as received"
+
+ byte lastByte = origBytes[length - 1];
+
+ if ((addressLength & 1) == 1) {
+ // Make sure the final unused BCD digit is 0xf
+ origBytes[length - 1] |= 0xf0;
+ }
+ address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
+ OFFSET_TOA, length - OFFSET_TOA);
+
+ // And restore origBytes
+ origBytes[length - 1] = lastByte;
+ }
+ }
+
+ @Override
+ public String getAddressString() {
+ return address;
+ }
+
+ /**
+ * Returns true if this is an alphanumeric address
+ */
+ @Override
+ public boolean isAlphanumeric() {
+ return ton == TON_ALPHANUMERIC;
+ }
+
+ @Override
+ public boolean isNetworkSpecific() {
+ return ton == TON_NETWORK;
+ }
+
+ /**
+ * Returns true of this is a valid CPHS voice message waiting indicator
+ * address
+ */
+ public boolean isCphsVoiceMessageIndicatorAddress() {
+ // CPHS-style MWI message
+ // See CPHS 4.7 B.4.2.1
+ //
+ // Basically:
+ //
+ // - Originating address should be 4 bytes long and alphanumeric
+ // - Decode will result with two chars:
+ // - Char 1
+ // 76543210
+ // ^ set/clear indicator (0 = clear)
+ // ^^^ type of indicator (000 = voice)
+ // ^^^^ must be equal to 0001
+ // - Char 2:
+ // 76543210
+ // ^ line number (0 = line 1)
+ // ^^^^^^^ set to 0
+ //
+ // Remember, since the alpha address is stored in 7-bit compact form,
+ // the "line number" is really the top bit of the first address value
+ // byte
+
+ return (origBytes[OFFSET_ADDRESS_LENGTH] & 0xff) == 4
+ && isAlphanumeric() && (origBytes[OFFSET_TOA] & 0x0f) == 0;
+ }
+
+ /**
+ * Returns true if this is a valid CPHS voice message waiting indicator
+ * address indicating a "set" of "indicator 1" of type "voice message
+ * waiting"
+ */
+ public boolean isCphsVoiceMessageSet() {
+ // 0x11 means "set" "voice message waiting" "indicator 1"
+ return isCphsVoiceMessageIndicatorAddress()
+ && (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x11;
+
+ }
+
+ /**
+ * Returns true if this is a valid CPHS voice message waiting indicator
+ * address indicating a "clear" of "indicator 1" of type "voice message
+ * waiting"
+ */
+ public boolean isCphsVoiceMessageClear() {
+ // 0x10 means "clear" "voice message waiting" "indicator 1"
+ return isCphsVoiceMessageIndicatorAddress()
+ && (origBytes[OFFSET_ADDRESS_VALUE] & 0xff) == 0x10;
+
+ }
+}
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 0000000..15ed810
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -0,0 +1,292 @@
+/*
+ * 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 com.android.internal.telephony.SmsConstants;
+
+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
+ */
+ public static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location,
+ byte[][] pdus) throws IllegalArgumentException {
+ if (header.isEtwsPrimaryNotification()) {
+ // ETSI TS 23.041 ETWS Primary Notification message
+ // ETWS primary message only contains 4 fields including serial number,
+ // message identifier, warning type, and warning security information.
+ // There is no field for the content/text. We hardcode "ETWS" in the
+ // text body so the user won't see an empty dialog without any text.
+ 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 = SmsConstants.ENCODING_7BIT;
+ language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
+ break;
+
+ case 0x01:
+ hasLanguageIndicator = true;
+ if ((dataCodingScheme & 0x0f) == 0x01) {
+ encoding = SmsConstants.ENCODING_16BIT;
+ } else {
+ encoding = SmsConstants.ENCODING_7BIT;
+ }
+ break;
+
+ case 0x02:
+ encoding = SmsConstants.ENCODING_7BIT;
+ language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
+ break;
+
+ case 0x03:
+ encoding = SmsConstants.ENCODING_7BIT;
+ break;
+
+ case 0x04:
+ case 0x05:
+ switch ((dataCodingScheme & 0x0c) >> 2) {
+ case 0x01:
+ encoding = SmsConstants.ENCODING_8BIT;
+ break;
+
+ case 0x02:
+ encoding = SmsConstants.ENCODING_16BIT;
+ break;
+
+ case 0x00:
+ default:
+ encoding = SmsConstants.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 = SmsConstants.ENCODING_8BIT;
+ } else {
+ encoding = SmsConstants.ENCODING_7BIT;
+ }
+ break;
+
+ default:
+ // Reserved values are to be treated as 7-bit
+ encoding = SmsConstants.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 SmsConstants.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 SmsConstants.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/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
new file mode 100644
index 0000000..f4f4036
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+
+/**
+ * SmsBroadcastConfigInfo defines one configuration of Cell Broadcast
+ * Message (CBM) to be received by the ME
+ *
+ * fromServiceId - toServiceId defines a range of CBM message identifiers
+ * whose value is 0x0000 - 0xFFFF as defined in TS 23.041 9.4.1.2.2 for GMS
+ * and 9.4.4.2.2 for UMTS. All other values can be treated as empty
+ * CBM message ID.
+ *
+ * fromCodeScheme - toCodeScheme defines a range of CBM data coding schemes
+ * whose value is 0x00 - 0xFF as defined in TS 23.041 9.4.1.2.3 for GMS
+ * and 9.4.4.2.3 for UMTS.
+ * All other values can be treated as empty CBM data coding scheme.
+ *
+ * selected false means message types specified in {@code <fromServiceId, toServiceId>}
+ * and {@code <fromCodeScheme, toCodeScheme>} are not accepted, while true means accepted.
+ *
+ */
+public final class SmsBroadcastConfigInfo {
+ private int mFromServiceId;
+ private int mToServiceId;
+ private int mFromCodeScheme;
+ private int mToCodeScheme;
+ private boolean mSelected;
+
+ /**
+ * Initialize the object from rssi and cid.
+ */
+ public SmsBroadcastConfigInfo(int fromId, int toId, int fromScheme,
+ int toScheme, boolean selected) {
+ mFromServiceId = fromId;
+ mToServiceId = toId;
+ mFromCodeScheme = fromScheme;
+ mToCodeScheme = toScheme;
+ mSelected = selected;
+ }
+
+ /**
+ * @param fromServiceId the fromServiceId to set
+ */
+ public void setFromServiceId(int fromServiceId) {
+ mFromServiceId = fromServiceId;
+ }
+
+ /**
+ * @return the fromServiceId
+ */
+ public int getFromServiceId() {
+ return mFromServiceId;
+ }
+
+ /**
+ * @param toServiceId the toServiceId to set
+ */
+ public void setToServiceId(int toServiceId) {
+ mToServiceId = toServiceId;
+ }
+
+ /**
+ * @return the toServiceId
+ */
+ public int getToServiceId() {
+ return mToServiceId;
+ }
+
+ /**
+ * @param fromCodeScheme the fromCodeScheme to set
+ */
+ public void setFromCodeScheme(int fromCodeScheme) {
+ mFromCodeScheme = fromCodeScheme;
+ }
+
+ /**
+ * @return the fromCodeScheme
+ */
+ public int getFromCodeScheme() {
+ return mFromCodeScheme;
+ }
+
+ /**
+ * @param toCodeScheme the toCodeScheme to set
+ */
+ public void setToCodeScheme(int toCodeScheme) {
+ mToCodeScheme = toCodeScheme;
+ }
+
+ /**
+ * @return the toCodeScheme
+ */
+ public int getToCodeScheme() {
+ return mToCodeScheme;
+ }
+
+ /**
+ * @param selected the selected to set
+ */
+ public void setSelected(boolean selected) {
+ mSelected = selected;
+ }
+
+ /**
+ * @return the selected
+ */
+ public boolean isSelected() {
+ return mSelected;
+ }
+
+ @Override
+ public String toString() {
+ return "SmsBroadcastConfigInfo: Id [" +
+ mFromServiceId + ',' + mToServiceId + "] Code [" +
+ mFromCodeScheme + ',' + mToCodeScheme + "] " +
+ (mSelected ? "ENABLED" : "DISABLED");
+ }
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
new file mode 100644
index 0000000..bce5680
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
@@ -0,0 +1,217 @@
+/*
+ * 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;
+
+/**
+ * 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 class SmsCbConstants {
+
+ /** Private constructor for utility class. */
+ private SmsCbConstants() { }
+
+ /** Channel 50 required by Brazil. ID 0~999 is allocated by GSMA */
+ public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_50
+ = 0x0032;
+
+ /** Channel 911 required by Taiwan NCC. ID 0~999 is allocated by GSMA */
+ public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_911
+ = 0x038F; // 911
+
+ /** Channel 919 required by Taiwan NCC and Israel. ID 0~999 is allocated by GSMA */
+ public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_919
+ = 0x0397; // 919
+
+ /** Channel 928 required by Israel. ID 0~999 is allocated by GSMA */
+ public static final int MESSAGE_ID_GSMA_ALLOCATED_CHANNEL_928
+ = 0x03A0; // 928
+
+ /** Start of PWS Message Identifier range (includes ETWS and CMAS). */
+ public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER
+ = 0x1100; // 4352
+
+ /** Bitmask for messages of ETWS type (including future extensions). */
+ public static final int MESSAGE_ID_ETWS_TYPE_MASK
+ = 0xFFF8;
+
+ /** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */
+ public static final int MESSAGE_ID_ETWS_TYPE
+ = 0x1100; // 4352
+
+ /** ETWS Message Identifier for earthquake warning message. */
+ public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING
+ = 0x1100; // 4352
+
+ /** ETWS Message Identifier for tsunami warning message. */
+ public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING
+ = 0x1101; // 4353
+
+ /** ETWS Message Identifier for earthquake and tsunami combined warning message. */
+ public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING
+ = 0x1102; // 4354
+
+ /** ETWS Message Identifier for test message. */
+ public static final int MESSAGE_ID_ETWS_TEST_MESSAGE
+ = 0x1103; // 4355
+
+ /** ETWS Message Identifier for messages related to other emergency types. */
+ public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE
+ = 0x1104; // 4356
+
+ /** Start of CMAS Message Identifier range. */
+ public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER
+ = 0x1112; // 4370
+
+ /** CMAS Message Identifier for Presidential Level alerts. */
+ public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL
+ = 0x1112; // 4370
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED
+ = 0x1113; // 4371
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY
+ = 0x1114; // 4372
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED
+ = 0x1115; // 4373
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY
+ = 0x1116; // 4374
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED
+ = 0x1117; // 4375
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY
+ = 0x1118; // 4376
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED
+ = 0x1119; // 4377
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY
+ = 0x111A; // 4378
+
+ /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */
+ public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY
+ = 0x111B; // 4379
+
+ /** CMAS Message Identifier for the Required Monthly Test. */
+ public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST
+ = 0x111C; // 4380
+
+ /** CMAS Message Identifier for CMAS Exercise. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE
+ = 0x111D; // 4381
+
+ /** CMAS Message Identifier for operator defined use. */
+ public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE
+ = 0x111E; // 4382
+
+ /** CMAS Message Identifier for Presidential Level alerts for additional languages
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE
+ = 0x111F; // 4383
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE
+ = 0x1120; // 4384
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE
+ = 0x1121; // 4385
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE
+ = 0x1122; // 4386
+
+ /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE
+ = 0x1123; // 4387
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE
+ = 0x1124; // 4388
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely
+ * for additional languages.*/
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE
+ = 0x1125; // 4389
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE
+ = 0x1126; // 4390
+
+ /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely
+ * for additional languages.*/
+ public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE
+ = 0x1127; // 4391
+
+ /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert)
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE
+ = 0x1128; // 4392
+
+ /** CMAS Message Identifier for the Required Monthly Test
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE
+ = 0x1129; // 4393
+
+ /** CMAS Message Identifier for CMAS Exercise
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE
+ = 0x112A; // 4394
+
+ /** CMAS Message Identifier for operator defined use
+ * for additional languages. */
+ public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE
+ = 0x112B; // 4395
+
+ /** End of CMAS Message Identifier range (including future extensions). */
+ public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER
+ = 0x112F; // 4399
+
+ /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
+ public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER
+ = 0x18FF; // 6399
+
+ /** ETWS serial number flag to activate the popup display. */
+ public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP
+ = 0x1000; // 4096
+
+ /** ETWS serial number flag to activate the emergency user alert. */
+ public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT
+ = 0x2000; // 8192
+}
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
new file mode 100644
index 0000000..d267ad2
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.gsm;
+
+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.
+ */
+public class SmsCbHeader {
+
+ /**
+ * Length of SMS-CB header
+ */
+ static final int PDU_HEADER_LENGTH = 6;
+
+ /**
+ * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
+ */
+ static final int FORMAT_GSM = 1;
+
+ /**
+ * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
+ */
+ static final int FORMAT_UMTS = 2;
+
+ /**
+ * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
+ */
+ static final int FORMAT_ETWS_PRIMARY = 3;
+
+ /**
+ * Message type value as defined in 3gpp TS 25.324, section 11.1.
+ */
+ private static final int MESSAGE_TYPE_CBS_MESSAGE = 1;
+
+ /**
+ * Length of GSM pdus
+ */
+ private static final int PDU_LENGTH_GSM = 88;
+
+ /**
+ * Maximum length of ETWS primary message GSM pdus
+ */
+ private static final int PDU_LENGTH_ETWS = 56;
+
+ private final int mGeographicalScope;
+
+ /** The serial number combines geographical scope, message code, and update number. */
+ private final int mSerialNumber;
+
+ /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
+ private final int mMessageIdentifier;
+
+ private final int mDataCodingScheme;
+
+ private final int mPageIndex;
+
+ private final int mNrOfPages;
+
+ private final int mFormat;
+
+ /** ETWS warning notification info. */
+ private final SmsCbEtwsInfo mEtwsInfo;
+
+ /** CMAS warning notification info. */
+ private final SmsCbCmasInfo mCmasInfo;
+
+ public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
+ if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
+ throw new IllegalArgumentException("Illegal PDU");
+ }
+
+ if (pdu.length <= PDU_LENGTH_GSM) {
+ // can be ETWS or GSM format.
+ // Per TS23.041 9.4.1.2 and 9.4.1.3.2, GSM and ETWS format both
+ // contain serial number which contains GS, Message Code, and Update Number
+ // per 9.4.1.2.1, and message identifier in same octets
+ mGeographicalScope = (pdu[0] & 0xc0) >>> 6;
+ mSerialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
+ mMessageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
+ if (isEtwsMessage() && pdu.length <= PDU_LENGTH_ETWS) {
+ mFormat = FORMAT_ETWS_PRIMARY;
+ mDataCodingScheme = -1;
+ mPageIndex = -1;
+ mNrOfPages = -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,
+ true, warningSecurityInfo);
+ mCmasInfo = null;
+ return; // skip the ETWS/CMAS initialization code for regular notifications
+ } else {
+ // GSM pdus are no more than 88 bytes
+ mFormat = FORMAT_GSM;
+ mDataCodingScheme = pdu[4] & 0xff;
+
+ // Check for invalid page parameter
+ int pageIndex = (pdu[5] & 0xf0) >>> 4;
+ int nrOfPages = pdu[5] & 0x0f;
+
+ if (pageIndex == 0 || nrOfPages == 0 || pageIndex > nrOfPages) {
+ pageIndex = 1;
+ nrOfPages = 1;
+ }
+
+ mPageIndex = pageIndex;
+ mNrOfPages = nrOfPages;
+ }
+ } 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
+ mFormat = FORMAT_UMTS;
+
+ int messageType = pdu[0];
+
+ if (messageType != MESSAGE_TYPE_CBS_MESSAGE) {
+ throw new IllegalArgumentException("Unsupported message type " + messageType);
+ }
+
+ mMessageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
+ mGeographicalScope = (pdu[3] & 0xc0) >>> 6;
+ mSerialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
+ mDataCodingScheme = pdu[5] & 0xff;
+
+ // We will always consider a UMTS message as having one single page
+ // since there's only one instance of the header, even though the
+ // actual payload may contain several pages.
+ mPageIndex = 1;
+ mNrOfPages = 1;
+ }
+
+ if (isEtwsMessage()) {
+ boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
+ boolean activatePopup = isEtwsPopupAlert();
+ int warningType = getEtwsWarningType();
+ mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
+ false, 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 mGeographicalScope;
+ }
+
+ int getSerialNumber() {
+ return mSerialNumber;
+ }
+
+ int getServiceCategory() {
+ return mMessageIdentifier;
+ }
+
+ int getDataCodingScheme() {
+ return mDataCodingScheme;
+ }
+
+ int getPageIndex() {
+ return mPageIndex;
+ }
+
+ int getNumberOfPages() {
+ return mNrOfPages;
+ }
+
+ 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 mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
+ && mMessageIdentifier <= 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 (mMessageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
+ == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
+ }
+
+ /**
+ * Return whether this broadcast is an ETWS primary notification.
+ * @return true if this message is an ETWS primary notification; false otherwise
+ */
+ boolean isEtwsPrimaryNotification() {
+ return mFormat == FORMAT_ETWS_PRIMARY;
+ }
+
+ /**
+ * Return whether this broadcast is in UMTS format.
+ * @return true if this message is in UMTS format; false otherwise
+ */
+ boolean isUmtsFormat() {
+ return mFormat == FORMAT_UMTS;
+ }
+
+ /**
+ * Return whether this message is a CMAS emergency message type.
+ * @return true if this message is CMAS emergency type; false otherwise
+ */
+ private boolean isCmasMessage() {
+ return mMessageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
+ && mMessageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
+ }
+
+ /**
+ * 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.
+ *
+ * @return true if the message code indicates a popup alert should be displayed
+ */
+ private boolean isEtwsPopupAlert() {
+ return (mSerialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
+ }
+
+ /**
+ * 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.
+ *
+ * @return true if the message code indicates an emergency user alert
+ */
+ private boolean isEtwsEmergencyUserAlert() {
+ return (mSerialNumber & 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 mMessageIdentifier - 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 (mMessageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE_LANGUAGE:
+ 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 (mMessageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+ 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 (mMessageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+ 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 (mMessageIdentifier) {
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
+
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY_LANGUAGE:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+ case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY_LANGUAGE:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
+
+ default:
+ return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "SmsCbHeader{GS=" + mGeographicalScope + ", serialNumber=0x" +
+ Integer.toHexString(mSerialNumber) +
+ ", messageIdentifier=0x" + Integer.toHexString(mMessageIdentifier) +
+ ", DCS=0x" + Integer.toHexString(mDataCodingScheme) +
+ ", page " + mPageIndex + " of " + mNrOfPages + '}';
+ }
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
new file mode 100644
index 0000000..582506a
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -0,0 +1,1369 @@
+/*
+ * Copyright (C) 2006 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.PhoneNumberUtils;
+import android.text.format.Time;
+import android.telephony.Rlog;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
+import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.SmsHeader;
+import com.android.internal.telephony.SmsMessageBase;
+import com.android.internal.telephony.Sms7BitEncodingTranslator;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+
+import static com.android.internal.telephony.SmsConstants.MessageClass;
+import static com.android.internal.telephony.SmsConstants.ENCODING_UNKNOWN;
+import static com.android.internal.telephony.SmsConstants.ENCODING_7BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_16BIT;
+import static com.android.internal.telephony.SmsConstants.ENCODING_KSC5601;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES;
+import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_BYTES_WITH_HEADER;
+
+/**
+ * A Short Message Service message.
+ *
+ */
+public class SmsMessage extends SmsMessageBase {
+ static final String LOG_TAG = "SmsMessage";
+ private static final boolean VDBG = false;
+
+ private MessageClass messageClass;
+
+ /**
+ * TP-Message-Type-Indicator
+ * 9.2.3
+ */
+ private int mMti;
+
+ /** TP-Protocol-Identifier (TP-PID) */
+ private int mProtocolIdentifier;
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ private int mDataCodingScheme;
+
+ // TP-Reply-Path
+ // e.g. 23.040 9.2.2.1
+ private boolean mReplyPathPresent = false;
+
+ /** The address of the receiver. */
+ private GsmSmsAddress mRecipientAddress;
+
+ /**
+ * TP-Status - status of a previously submitted SMS.
+ * This field applies to SMS-STATUS-REPORT messages. 0 indicates success;
+ * see TS 23.040, 9.2.3.15 for description of other possible values.
+ */
+ private int mStatus;
+
+ /**
+ * TP-Status - status of a previously submitted SMS.
+ * This field is true iff the message is a SMS-STATUS-REPORT message.
+ */
+ private boolean mIsStatusReportMessage = false;
+
+ private int mVoiceMailCount = 0;
+
+ public static class SubmitPdu extends SubmitPduBase {
+ }
+
+ /**
+ * Create an SmsMessage from a raw PDU.
+ */
+ public static SmsMessage createFromPdu(byte[] pdu) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ } catch (OutOfMemoryError e) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed with out of memory: ", e);
+ return null;
+ }
+ }
+
+ /**
+ * 3GPP TS 23.040 9.2.3.9 specifies that Type Zero messages are indicated
+ * by TP_PID field set to value 0x40
+ */
+ public boolean isTypeZero() {
+ return (mProtocolIdentifier == 0x40);
+ }
+
+ /**
+ * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the
+ * +CMT unsolicited response (PDU mode, of course)
+ * +CMT: [<alpha>],<length><CR><LF><pdu>
+ *
+ * Only public for debugging
+ *
+ * {@hide}
+ */
+ public static SmsMessage newFromCMT(byte[] pdu) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /** @hide */
+ public static SmsMessage newFromCDS(byte[] pdu) {
+ try {
+ SmsMessage msg = new SmsMessage();
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Create an SmsMessage from an SMS EF record.
+ *
+ * @param index Index of SMS record. This should be index in ArrayList
+ * returned by SmsManager.getAllMessagesFromSim + 1.
+ * @param data Record data.
+ * @return An SmsMessage representing the record.
+ *
+ * @hide
+ */
+ public static SmsMessage createFromEfRecord(int index, byte[] data) {
+ try {
+ SmsMessage msg = new SmsMessage();
+
+ msg.mIndexOnIcc = index;
+
+ // First byte is status: RECEIVED_READ, RECEIVED_UNREAD, STORED_SENT,
+ // or STORED_UNSENT
+ // See TS 51.011 10.5.3
+ if ((data[0] & 1) == 0) {
+ Rlog.w(LOG_TAG,
+ "SMS parsing failed: Trying to parse a free record");
+ return null;
+ } else {
+ msg.mStatusOnIcc = data[0] & 0x07;
+ }
+
+ int size = data.length - 1;
+
+ // Note: Data may include trailing FF's. That's OK; message
+ // should still parse correctly.
+ byte[] pdu = new byte[size];
+ System.arraycopy(data, 1, pdu, 0, size);
+ msg.parsePdu(pdu);
+ return msg;
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
+ * length in bytes (not hex chars) less the SMSC header
+ */
+ public static int getTPLayerLengthForPDU(String pdu) {
+ int len = pdu.length() / 2;
+ int smscLen = Integer.parseInt(pdu.substring(0, 2), 16);
+
+ return len - smscLen - 1;
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header) {
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
+ ENCODING_UNKNOWN, 0, 0);
+ }
+
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message using the
+ * specified encoding.
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @param encoding Encoding defined by constants in
+ * com.android.internal.telephony.SmsConstants.ENCODING_*
+ * @param languageTable
+ * @param languageShiftTable
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ * @hide
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested, byte[] header, int encoding,
+ int languageTable, int languageShiftTable) {
+
+ // Perform null parameter checks.
+ if (message == null || destinationAddress == null) {
+ return null;
+ }
+
+ if (encoding == ENCODING_UNKNOWN) {
+ // Find the best encoding to use
+ TextEncodingDetails ted = calculateLength(message, false);
+ encoding = ted.codeUnitSize;
+ languageTable = ted.languageTable;
+ languageShiftTable = ted.languageShiftTable;
+
+ if (encoding == ENCODING_7BIT &&
+ (languageTable != 0 || languageShiftTable != 0)) {
+ if (header != null) {
+ SmsHeader smsHeader = SmsHeader.fromByteArray(header);
+ if (smsHeader.languageTable != languageTable
+ || smsHeader.languageShiftTable != languageShiftTable) {
+ Rlog.w(LOG_TAG, "Updating language table in SMS header: "
+ + smsHeader.languageTable + " -> " + languageTable + ", "
+ + smsHeader.languageShiftTable + " -> " + languageShiftTable);
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ header = SmsHeader.toByteArray(smsHeader);
+ }
+ } else {
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ header = SmsHeader.toByteArray(smsHeader);
+ }
+ }
+ }
+
+ SubmitPdu ret = new SubmitPdu();
+ // MTI = SMS-SUBMIT, UDHI = header != null
+ byte mtiByte = (byte)(0x01 | (header != null ? 0x40 : 0x00));
+ ByteArrayOutputStream bo = getSubmitPduHead(
+ scAddress, destinationAddress, mtiByte,
+ statusReportRequested, ret);
+
+ // User Data (and length)
+ byte[] userData;
+ try {
+ if (encoding == ENCODING_7BIT) {
+ userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header,
+ languageTable, languageShiftTable);
+ } else { //assume UCS-2
+ try {
+ userData = encodeUCS2(message, header);
+ } catch(UnsupportedEncodingException uex) {
+ Rlog.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+ } catch (EncodeException ex) {
+ // Encoding to the 7-bit alphabet failed. Let's see if we can
+ // send it as a UCS-2 encoded message
+ try {
+ userData = encodeUCS2(message, header);
+ encoding = ENCODING_16BIT;
+ } catch(UnsupportedEncodingException uex) {
+ Rlog.e(LOG_TAG,
+ "Implausible UnsupportedEncodingException ",
+ uex);
+ return null;
+ }
+ }
+
+ if (encoding == ENCODING_7BIT) {
+ if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
+ // Message too long
+ Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " septets)");
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // Default encoding, uncompressed
+ // To test writing messages to the SIM card, change this value 0x00
+ // to 0x12, which means "bits 1 and 0 contain message class, and the
+ // class is 2". Note that this takes effect for the sender. In other
+ // words, messages sent by the phone with this change will end up on
+ // the receiver's SIM card. You can then send messages to yourself
+ // (on a phone with this change) and they'll end up on the SIM card.
+ bo.write(0x00);
+ } else { // assume UCS-2
+ if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
+ // Message too long
+ Rlog.e(LOG_TAG, "Message too long (" + (0xff & userData[0]) + " bytes)");
+ return null;
+ }
+ // TP-Data-Coding-Scheme
+ // UCS-2 encoding, uncompressed
+ bo.write(0x08);
+ }
+
+ // (no TP-Validity-Period)
+ bo.write(userData, 0, userData.length);
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+ /**
+ * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
+ *
+ * @return encoded message as UCS2
+ * @throws UnsupportedEncodingException
+ */
+ private static byte[] encodeUCS2(String message, byte[] header)
+ throws UnsupportedEncodingException {
+ byte[] userData, textPart;
+ textPart = message.getBytes("utf-16be");
+
+ if (header != null) {
+ // Need 1 byte for UDHL
+ userData = new byte[header.length + textPart.length + 1];
+
+ userData[0] = (byte)header.length;
+ System.arraycopy(header, 0, userData, 1, header.length);
+ System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+ }
+ else {
+ userData = textPart;
+ }
+ byte[] ret = new byte[userData.length+1];
+ ret[0] = (byte) (userData.length & 0xff );
+ System.arraycopy(userData, 0, ret, 1, userData.length);
+ return ret;
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a destination address and a message
+ *
+ * @param scAddress Service Centre address. Null means use default.
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, String message,
+ boolean statusReportRequested) {
+
+ return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, null);
+ }
+
+ /**
+ * Get an SMS-SUBMIT PDU for a data message to a destination address & port
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param destinationPort the port to deliver the message to at the
+ * destination
+ * @param data the data for the message
+ * @return a <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message.
+ * Returns null on encode error.
+ */
+ public static SubmitPdu getSubmitPdu(String scAddress,
+ String destinationAddress, int destinationPort, byte[] data,
+ boolean statusReportRequested) {
+
+ SmsHeader.PortAddrs portAddrs = new SmsHeader.PortAddrs();
+ portAddrs.destPort = destinationPort;
+ portAddrs.origPort = 0;
+ portAddrs.areEightBits = false;
+
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.portAddrs = portAddrs;
+
+ byte[] smsHeaderData = SmsHeader.toByteArray(smsHeader);
+
+ if ((data.length + smsHeaderData.length + 1) > MAX_USER_DATA_BYTES) {
+ Rlog.e(LOG_TAG, "SMS data message may only contain "
+ + (MAX_USER_DATA_BYTES - smsHeaderData.length - 1) + " bytes");
+ return null;
+ }
+
+ SubmitPdu ret = new SubmitPdu();
+ ByteArrayOutputStream bo = getSubmitPduHead(
+ scAddress, destinationAddress, (byte) 0x41, // MTI = SMS-SUBMIT,
+ // TP-UDHI = true
+ statusReportRequested, ret);
+
+ // TP-Data-Coding-Scheme
+ // No class, 8 bit data
+ bo.write(0x04);
+
+ // (no TP-Validity-Period)
+
+ // Total size
+ bo.write(data.length + smsHeaderData.length + 1);
+
+ // User data header
+ bo.write(smsHeaderData.length);
+ bo.write(smsHeaderData, 0, smsHeaderData.length);
+
+ // User data
+ bo.write(data, 0, data.length);
+
+ ret.encodedMessage = bo.toByteArray();
+ return ret;
+ }
+
+ /**
+ * Create the beginning of a SUBMIT PDU. This is the part of the
+ * SUBMIT PDU that is common to the two versions of {@link #getSubmitPdu},
+ * one of which takes a byte array and the other of which takes a
+ * <code>String</code>.
+ *
+ * @param scAddress Service Centre address. null == use default
+ * @param destinationAddress the address of the destination for the message
+ * @param mtiByte
+ * @param ret <code>SubmitPdu</code> containing the encoded SC
+ * address, if applicable, and the encoded message
+ */
+ private static ByteArrayOutputStream getSubmitPduHead(
+ String scAddress, String destinationAddress, byte mtiByte,
+ boolean statusReportRequested, SubmitPdu ret) {
+ ByteArrayOutputStream bo = new ByteArrayOutputStream(
+ MAX_USER_DATA_BYTES + 40);
+
+ // SMSC address with length octet, or 0
+ if (scAddress == null) {
+ ret.encodedScAddress = null;
+ } else {
+ ret.encodedScAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
+ scAddress);
+ }
+
+ // TP-Message-Type-Indicator (and friends)
+ if (statusReportRequested) {
+ // Set TP-Status-Report-Request bit.
+ mtiByte |= 0x20;
+ if (VDBG) Rlog.d(LOG_TAG, "SMS status report requested");
+ }
+ bo.write(mtiByte);
+
+ // space for TP-Message-Reference
+ bo.write(0);
+
+ byte[] daBytes;
+
+ daBytes = PhoneNumberUtils.networkPortionToCalledPartyBCD(destinationAddress);
+
+ // destination address length in BCD digits, ignoring TON byte and pad
+ // TODO Should be better.
+ bo.write((daBytes.length - 1) * 2
+ - ((daBytes[daBytes.length - 1] & 0xf0) == 0xf0 ? 1 : 0));
+
+ // destination address
+ bo.write(daBytes, 0, daBytes.length);
+
+ // TP-Protocol-Identifier
+ bo.write(0);
+ return bo;
+ }
+
+ private static class PduParser {
+ byte mPdu[];
+ int mCur;
+ SmsHeader mUserDataHeader;
+ byte[] mUserData;
+ int mUserDataSeptetPadding;
+
+ PduParser(byte[] pdu) {
+ mPdu = pdu;
+ mCur = 0;
+ mUserDataSeptetPadding = 0;
+ }
+
+ /**
+ * Parse and return the SC address prepended to SMS messages coming via
+ * the TS 27.005 / AT interface. Returns null on invalid address
+ */
+ String getSCAddress() {
+ int len;
+ String ret;
+
+ // length of SC Address
+ len = getByte();
+
+ if (len == 0) {
+ // no SC address
+ ret = null;
+ } else {
+ // SC address
+ try {
+ ret = PhoneNumberUtils
+ .calledPartyBCDToString(mPdu, mCur, len);
+ } catch (RuntimeException tr) {
+ Rlog.d(LOG_TAG, "invalid SC address: ", tr);
+ ret = null;
+ }
+ }
+
+ mCur += len;
+
+ return ret;
+ }
+
+ /**
+ * returns non-sign-extended byte value
+ */
+ int getByte() {
+ return mPdu[mCur++] & 0xff;
+ }
+
+ /**
+ * Any address except the SC address (eg, originating address) See TS
+ * 23.040 9.1.2.5
+ */
+ GsmSmsAddress getAddress() {
+ GsmSmsAddress ret;
+
+ // "The Address-Length field is an integer representation of
+ // the number field, i.e. excludes any semi-octet containing only
+ // fill bits."
+ // The TOA field is not included as part of this
+ int addressLength = mPdu[mCur] & 0xff;
+ int lengthBytes = 2 + (addressLength + 1) / 2;
+
+ try {
+ ret = new GsmSmsAddress(mPdu, mCur, lengthBytes);
+ } catch (ParseException e) {
+ ret = null;
+ //This is caught by createFromPdu(byte[] pdu)
+ throw new RuntimeException(e.getMessage());
+ }
+
+ mCur += lengthBytes;
+
+ return ret;
+ }
+
+ /**
+ * Parses an SC timestamp and returns a currentTimeMillis()-style
+ * timestamp
+ */
+
+ long getSCTimestampMillis() {
+ // TP-Service-Centre-Time-Stamp
+ int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int month = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int day = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int hour = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int minute = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+ int second = IccUtils.gsmBcdByteToInt(mPdu[mCur++]);
+
+ // 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 = mPdu[mCur++];
+
+ // 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);
+ }
+
+ /**
+ * Pulls the user data out of the PDU, and separates the payload from
+ * the header if there is one.
+ *
+ * @param hasUserDataHeader true if there is a user data header
+ * @param dataInSeptets true if the data payload is in septets instead
+ * of octets
+ * @return the number of septets or octets in the user data payload
+ */
+ int constructUserData(boolean hasUserDataHeader, boolean dataInSeptets) {
+ int offset = mCur;
+ int userDataLength = mPdu[offset++] & 0xff;
+ int headerSeptets = 0;
+ int userDataHeaderLength = 0;
+
+ if (hasUserDataHeader) {
+ userDataHeaderLength = mPdu[offset++] & 0xff;
+
+ byte[] udh = new byte[userDataHeaderLength];
+ System.arraycopy(mPdu, offset, udh, 0, userDataHeaderLength);
+ mUserDataHeader = SmsHeader.fromByteArray(udh);
+ offset += userDataHeaderLength;
+
+ int headerBits = (userDataHeaderLength + 1) * 8;
+ headerSeptets = headerBits / 7;
+ headerSeptets += (headerBits % 7) > 0 ? 1 : 0;
+ mUserDataSeptetPadding = (headerSeptets * 7) - headerBits;
+ }
+
+ int bufferLen;
+ if (dataInSeptets) {
+ /*
+ * Here we just create the user data length to be the remainder of
+ * the pdu minus the user data header, since userDataLength means
+ * the number of uncompressed septets.
+ */
+ bufferLen = mPdu.length - offset;
+ } else {
+ /*
+ * userDataLength is the count of octets, so just subtract the
+ * user data header.
+ */
+ bufferLen = userDataLength - (hasUserDataHeader ? (userDataHeaderLength + 1) : 0);
+ if (bufferLen < 0) {
+ bufferLen = 0;
+ }
+ }
+
+ mUserData = new byte[bufferLen];
+ System.arraycopy(mPdu, offset, mUserData, 0, mUserData.length);
+ mCur = offset;
+
+ if (dataInSeptets) {
+ // Return the number of septets
+ int count = userDataLength - headerSeptets;
+ // If count < 0, return 0 (means UDL was probably incorrect)
+ return count < 0 ? 0 : count;
+ } else {
+ // Return the number of octets
+ return mUserData.length;
+ }
+ }
+
+ /**
+ * Returns the user data payload, not including the headers
+ *
+ * @return the user data payload, not including the headers
+ */
+ byte[] getUserData() {
+ return mUserData;
+ }
+
+ /**
+ * Returns an object representing the user data headers
+ *
+ * {@hide}
+ */
+ SmsHeader getUserDataHeader() {
+ return mUserDataHeader;
+ }
+
+ /**
+ * Interprets the user data payload as packed GSM 7bit characters, and
+ * decodes them into a String.
+ *
+ * @param septetCount the number of septets in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataGSM7Bit(int septetCount, int languageTable,
+ int languageShiftTable) {
+ String ret;
+
+ ret = GsmAlphabet.gsm7BitPackedToString(mPdu, mCur, septetCount,
+ mUserDataSeptetPadding, languageTable, languageShiftTable);
+
+ mCur += (septetCount * 7) / 8;
+
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as pack GSM 8-bit (a GSM alphabet string that's
+ * stored in 8-bit unpacked format) characters, and decodes them into a String.
+ *
+ * @param byteCount the number of byest in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataGSM8bit(int byteCount) {
+ String ret;
+
+ ret = GsmAlphabet.gsm8BitUnpackedToString(mPdu, mCur, byteCount);
+
+ mCur += byteCount;
+
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as UCS2 characters, and
+ * decodes them into a String.
+ *
+ * @param byteCount the number of bytes in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataUCS2(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(mPdu, mCur, byteCount, "utf-16");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ mCur += byteCount;
+ return ret;
+ }
+
+ /**
+ * Interprets the user data payload as KSC-5601 characters, and
+ * decodes them into a String.
+ *
+ * @param byteCount the number of bytes in the user data payload
+ * @return a String with the decoded characters
+ */
+ String getUserDataKSC5601(int byteCount) {
+ String ret;
+
+ try {
+ ret = new String(mPdu, mCur, byteCount, "KSC5601");
+ } catch (UnsupportedEncodingException ex) {
+ ret = "";
+ Rlog.e(LOG_TAG, "implausible UnsupportedEncodingException", ex);
+ }
+
+ mCur += byteCount;
+ return ret;
+ }
+
+ boolean moreDataPresent() {
+ return (mPdu.length > mCur);
+ }
+ }
+
+ /**
+ * Calculates the number of SMS's required to encode the message body and
+ * the number of characters remaining until the next message.
+ *
+ * @param msgBody the message to encode
+ * @param use7bitOnly ignore (but still count) illegal characters if true
+ * @return TextEncodingDetails
+ */
+ public static TextEncodingDetails calculateLength(CharSequence msgBody,
+ boolean use7bitOnly) {
+ CharSequence newMsgBody = null;
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.R.bool.config_sms_force_7bit_encoding)) {
+ newMsgBody = Sms7BitEncodingTranslator.translate(msgBody);
+ }
+ if (TextUtils.isEmpty(newMsgBody)) {
+ newMsgBody = msgBody;
+ }
+ TextEncodingDetails ted = GsmAlphabet.countGsmSeptets(newMsgBody, use7bitOnly);
+ if (ted == null) {
+ return SmsMessageBase.calcUnicodeEncodingDetails(newMsgBody);
+ }
+ return ted;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getProtocolIdentifier() {
+ return mProtocolIdentifier;
+ }
+
+ /**
+ * Returns the TP-Data-Coding-Scheme byte, for acknowledgement of SMS-PP download messages.
+ * @return the TP-DCS field of the SMS header
+ */
+ int getDataCodingScheme() {
+ return mDataCodingScheme;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReplace() {
+ return (mProtocolIdentifier & 0xc0) == 0x40
+ && (mProtocolIdentifier & 0x3f) > 0
+ && (mProtocolIdentifier & 0x3f) < 8;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isCphsMwiMessage() {
+ return ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear()
+ || ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMWIClearMessage() {
+ if (mIsMwi && !mMwiSense) {
+ return true;
+ }
+
+ return mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageClear();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMWISetMessage() {
+ if (mIsMwi && mMwiSense) {
+ return true;
+ }
+
+ return mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isMwiDontStore() {
+ if (mIsMwi && mMwiDontStore) {
+ return true;
+ }
+
+ if (isCphsMwiMessage()) {
+ // See CPHS 4.2 Section B.4.2.1
+ // If the user data is a single space char, do not store
+ // the message. Otherwise, store and display as usual
+ if (" ".equals(getMessageBody())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isStatusReportMessage() {
+ return mIsStatusReportMessage;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isReplyPathPresent() {
+ return mReplyPathPresent;
+ }
+
+ /**
+ * TS 27.005 3.1, <pdu> definition "In the case of SMS: 3GPP TS 24.011 [6]
+ * SC address followed by 3GPP TS 23.040 [3] TPDU in hexadecimal format:
+ * ME/TA converts each octet of TP data unit into two IRA character long
+ * hex number (e.g. octet with integer value 42 is presented to TE as two
+ * characters 2A (IRA 50 and 65))" ...in the case of cell broadcast,
+ * something else...
+ */
+ private void parsePdu(byte[] pdu) {
+ mPdu = pdu;
+ // Rlog.d(LOG_TAG, "raw sms message:");
+ // Rlog.d(LOG_TAG, s);
+
+ PduParser p = new PduParser(pdu);
+
+ mScAddress = p.getSCAddress();
+
+ if (mScAddress != null) {
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC address: " + mScAddress);
+ }
+
+ // TODO(mkf) support reply path, user data header indicator
+
+ // TP-Message-Type-Indicator
+ // 9.2.3
+ int firstByte = p.getByte();
+
+ mMti = firstByte & 0x3;
+ switch (mMti) {
+ // TP-Message-Type-Indicator
+ // 9.2.3
+ case 0:
+ case 3: //GSM 03.40 9.2.3.1: MTI == 3 is Reserved.
+ //This should be processed in the same way as MTI == 0 (Deliver)
+ parseSmsDeliver(p, firstByte);
+ break;
+ case 1:
+ parseSmsSubmit(p, firstByte);
+ break;
+ case 2:
+ parseSmsStatusReport(p, firstByte);
+ break;
+ default:
+ // TODO(mkf) the rest of these
+ throw new RuntimeException("Unsupported message type");
+ }
+ }
+
+ /**
+ * Parses a SMS-STATUS-REPORT message.
+ *
+ * @param p A PduParser, cued past the first byte.
+ * @param firstByte The first byte of the PDU, which contains MTI, etc.
+ */
+ private void parseSmsStatusReport(PduParser p, int firstByte) {
+ mIsStatusReportMessage = true;
+
+ // TP-Message-Reference
+ mMessageRef = p.getByte();
+ // TP-Recipient-Address
+ mRecipientAddress = p.getAddress();
+ // TP-Service-Centre-Time-Stamp
+ mScTimeMillis = p.getSCTimestampMillis();
+ p.getSCTimestampMillis();
+ // TP-Status
+ mStatus = p.getByte();
+
+ // The following are optional fields that may or may not be present.
+ if (p.moreDataPresent()) {
+ // TP-Parameter-Indicator
+ int extraParams = p.getByte();
+ int moreExtraParams = extraParams;
+ while ((moreExtraParams & 0x80) != 0) {
+ // We only know how to parse a few extra parameters, all
+ // indicated in the first TP-PI octet, so skip over any
+ // additional TP-PI octets.
+ moreExtraParams = p.getByte();
+ }
+ // As per 3GPP 23.040 section 9.2.3.27 TP-Parameter-Indicator,
+ // only process the byte if the reserved bits (bits3 to 6) are zero.
+ if ((extraParams & 0x78) == 0) {
+ // TP-Protocol-Identifier
+ if ((extraParams & 0x01) != 0) {
+ mProtocolIdentifier = p.getByte();
+ }
+ // TP-Data-Coding-Scheme
+ if ((extraParams & 0x02) != 0) {
+ mDataCodingScheme = p.getByte();
+ }
+ // TP-User-Data-Length (implies existence of TP-User-Data)
+ if ((extraParams & 0x04) != 0) {
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+ parseUserData(p, hasUserDataHeader);
+ }
+ }
+ }
+ }
+
+ private void parseSmsDeliver(PduParser p, int firstByte) {
+ mReplyPathPresent = (firstByte & 0x80) == 0x80;
+
+ mOriginatingAddress = p.getAddress();
+
+ if (mOriginatingAddress != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS originating address: "
+ + mOriginatingAddress.address);
+ }
+
+ // TP-Protocol-Identifier (TP-PID)
+ // TS 23.040 9.2.3.9
+ mProtocolIdentifier = p.getByte();
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ mDataCodingScheme = p.getByte();
+
+ if (VDBG) {
+ Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+ + " data coding scheme: " + mDataCodingScheme);
+ }
+
+ mScTimeMillis = p.getSCTimestampMillis();
+
+ if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis);
+
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+ parseUserData(p, hasUserDataHeader);
+ }
+
+ /**
+ * Parses a SMS-SUBMIT message.
+ *
+ * @param p A PduParser, cued past the first byte.
+ * @param firstByte The first byte of the PDU, which contains MTI, etc.
+ */
+ private void parseSmsSubmit(PduParser p, int firstByte) {
+ mReplyPathPresent = (firstByte & 0x80) == 0x80;
+
+ // TP-MR (TP-Message Reference)
+ mMessageRef = p.getByte();
+
+ mRecipientAddress = p.getAddress();
+
+ if (mRecipientAddress != null) {
+ if (VDBG) Rlog.v(LOG_TAG, "SMS recipient address: " + mRecipientAddress.address);
+ }
+
+ // TP-Protocol-Identifier (TP-PID)
+ // TS 23.040 9.2.3.9
+ mProtocolIdentifier = p.getByte();
+
+ // TP-Data-Coding-Scheme
+ // see TS 23.038
+ mDataCodingScheme = p.getByte();
+
+ if (VDBG) {
+ Rlog.v(LOG_TAG, "SMS TP-PID:" + mProtocolIdentifier
+ + " data coding scheme: " + mDataCodingScheme);
+ }
+
+ // TP-Validity-Period-Format
+ int validityPeriodLength = 0;
+ int validityPeriodFormat = ((firstByte>>3) & 0x3);
+ if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/
+ {
+ validityPeriodLength = 0;
+ }
+ else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/
+ {
+ validityPeriodLength = 1;
+ }
+ else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/
+ {
+ validityPeriodLength = 7;
+ }
+
+ // TP-Validity-Period is not used on phone, so just ignore it for now.
+ while (validityPeriodLength-- > 0)
+ {
+ p.getByte();
+ }
+
+ boolean hasUserDataHeader = (firstByte & 0x40) == 0x40;
+
+ parseUserData(p, hasUserDataHeader);
+ }
+
+ /**
+ * Parses the User Data of an SMS.
+ *
+ * @param p The current PduParser.
+ * @param hasUserDataHeader Indicates whether a header is present in the
+ * User Data.
+ */
+ private void parseUserData(PduParser p, boolean hasUserDataHeader) {
+ boolean hasMessageClass = false;
+ boolean userDataCompressed = false;
+
+ int encodingType = ENCODING_UNKNOWN;
+
+ // Look up the data encoding scheme
+ if ((mDataCodingScheme & 0x80) == 0) {
+ userDataCompressed = (0 != (mDataCodingScheme & 0x20));
+ hasMessageClass = (0 != (mDataCodingScheme & 0x10));
+
+ if (userDataCompressed) {
+ Rlog.w(LOG_TAG, "4 - Unsupported SMS data coding scheme "
+ + "(compression) " + (mDataCodingScheme & 0xff));
+ } else {
+ switch ((mDataCodingScheme >> 2) & 0x3) {
+ case 0: // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ break;
+
+ case 2: // UCS 2 (16bit)
+ encodingType = ENCODING_16BIT;
+ break;
+
+ case 1: // 8 bit data
+ //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+ //that's stored in 8-bit unpacked format) characters.
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.
+ R.bool.config_sms_decode_gsm_8bit_data)) {
+ encodingType = ENCODING_8BIT;
+ break;
+ }
+
+ case 3: // reserved
+ Rlog.w(LOG_TAG, "1 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ encodingType = ENCODING_8BIT;
+ break;
+ }
+ }
+ } else if ((mDataCodingScheme & 0xf0) == 0xf0) {
+ hasMessageClass = true;
+ userDataCompressed = false;
+
+ if (0 == (mDataCodingScheme & 0x04)) {
+ // GSM 7 bit default alphabet
+ encodingType = ENCODING_7BIT;
+ } else {
+ // 8 bit data
+ encodingType = ENCODING_8BIT;
+ }
+ } else if ((mDataCodingScheme & 0xF0) == 0xC0
+ || (mDataCodingScheme & 0xF0) == 0xD0
+ || (mDataCodingScheme & 0xF0) == 0xE0) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+
+ // 0xC0 == 7 bit, don't store
+ // 0xD0 == 7 bit, store
+ // 0xE0 == UCS-2, store
+
+ if ((mDataCodingScheme & 0xF0) == 0xE0) {
+ encodingType = ENCODING_16BIT;
+ } else {
+ encodingType = ENCODING_7BIT;
+ }
+
+ userDataCompressed = false;
+ boolean active = ((mDataCodingScheme & 0x08) == 0x08);
+ // bit 0x04 reserved
+
+ // VM - If TP-UDH is present, these values will be overwritten
+ if ((mDataCodingScheme & 0x03) == 0x00) {
+ mIsMwi = true; /* Indicates vmail */
+ mMwiSense = active;/* Indicates vmail notification set/clear */
+ mMwiDontStore = ((mDataCodingScheme & 0xF0) == 0xC0);
+
+ /* Set voice mail count based on notification bit */
+ if (active == true) {
+ mVoiceMailCount = -1; // unknown number of messages waiting
+ } else {
+ mVoiceMailCount = 0; // no unread messages
+ }
+
+ Rlog.w(LOG_TAG, "MWI in DCS for Vmail. DCS = "
+ + (mDataCodingScheme & 0xff) + " Dont store = "
+ + mMwiDontStore + " vmail count = " + mVoiceMailCount);
+
+ } else {
+ mIsMwi = false;
+ Rlog.w(LOG_TAG, "MWI in DCS for fax/email/other: "
+ + (mDataCodingScheme & 0xff));
+ }
+ } else if ((mDataCodingScheme & 0xC0) == 0x80) {
+ // 3GPP TS 23.038 V7.0.0 (2006-03) section 4
+ // 0x80..0xBF == Reserved coding groups
+ if (mDataCodingScheme == 0x84) {
+ // This value used for KSC5601 by carriers in Korea.
+ encodingType = ENCODING_KSC5601;
+ } else {
+ Rlog.w(LOG_TAG, "5 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ }
+ } else {
+ Rlog.w(LOG_TAG, "3 - Unsupported SMS data coding scheme "
+ + (mDataCodingScheme & 0xff));
+ }
+
+ // set both the user data and the user data header.
+ int count = p.constructUserData(hasUserDataHeader,
+ encodingType == ENCODING_7BIT);
+ this.mUserData = p.getUserData();
+ this.mUserDataHeader = p.getUserDataHeader();
+
+ /*
+ * Look for voice mail indication in TP_UDH TS23.040 9.2.3.24
+ * ieid = 1 (0x1) (SPECIAL_SMS_MSG_IND)
+ * ieidl =2 octets
+ * ieda msg_ind_type = 0x00 (voice mail; discard sms )or
+ * = 0x80 (voice mail; store sms)
+ * msg_count = 0x00 ..0xFF
+ */
+ if (hasUserDataHeader && (mUserDataHeader.specialSmsMsgList.size() != 0)) {
+ for (SmsHeader.SpecialSmsMsg msg : mUserDataHeader.specialSmsMsgList) {
+ int msgInd = msg.msgIndType & 0xff;
+ /*
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ * bits 1 0 : basic message indication type
+ * bits 4 3 2 : extended message indication type
+ * bits 6 5 : Profile id bit 7 storage type
+ */
+ if ((msgInd == 0) || (msgInd == 0x80)) {
+ mIsMwi = true;
+ if (msgInd == 0x80) {
+ /* Store message because TP_UDH indicates so*/
+ mMwiDontStore = false;
+ } else if (mMwiDontStore == false) {
+ /* Storage bit is not set by TP_UDH
+ * Check for conflict
+ * between message storage bit in TP_UDH
+ * & DCS. The message shall be stored if either of
+ * the one indicates so.
+ * TS 23.040 V6.8.1 Sec 9.2.3.24.2
+ */
+ if (!((((mDataCodingScheme & 0xF0) == 0xD0)
+ || ((mDataCodingScheme & 0xF0) == 0xE0))
+ && ((mDataCodingScheme & 0x03) == 0x00))) {
+ /* Even DCS did not have voice mail with Storage bit
+ * 3GPP TS 23.038 V7.0.0 section 4
+ * So clear this flag*/
+ mMwiDontStore = true;
+ }
+ }
+
+ mVoiceMailCount = msg.msgCount & 0xff;
+
+ /*
+ * In the event of a conflict between message count setting
+ * and DCS then the Message Count in the TP-UDH shall
+ * override the indication in the TP-DCS. Set voice mail
+ * notification based on count in TP-UDH
+ */
+ if (mVoiceMailCount > 0)
+ mMwiSense = true;
+ else
+ mMwiSense = false;
+
+ Rlog.w(LOG_TAG, "MWI in TP-UDH for Vmail. Msg Ind = " + msgInd
+ + " Dont store = " + mMwiDontStore + " Vmail count = "
+ + mVoiceMailCount);
+
+ /*
+ * There can be only one IE for each type of message
+ * indication in TP_UDH. In the event they are duplicated
+ * last occurence will be used. Hence the for loop
+ */
+ } else {
+ Rlog.w(LOG_TAG, "TP_UDH fax/email/"
+ + "extended msg/multisubscriber profile. Msg Ind = " + msgInd);
+ }
+ } // end of for
+ } // end of if UDH
+
+ switch (encodingType) {
+ case ENCODING_UNKNOWN:
+ mMessageBody = null;
+ break;
+
+ case ENCODING_8BIT:
+ //Support decoding the user data payload as pack GSM 8-bit (a GSM alphabet string
+ //that's stored in 8-bit unpacked format) characters.
+ Resources r = Resources.getSystem();
+ if (r.getBoolean(com.android.internal.
+ R.bool.config_sms_decode_gsm_8bit_data)) {
+ mMessageBody = p.getUserDataGSM8bit(count);
+ } else {
+ mMessageBody = null;
+ }
+ break;
+
+ case ENCODING_7BIT:
+ mMessageBody = p.getUserDataGSM7Bit(count,
+ hasUserDataHeader ? mUserDataHeader.languageTable : 0,
+ hasUserDataHeader ? mUserDataHeader.languageShiftTable : 0);
+ break;
+
+ case ENCODING_16BIT:
+ mMessageBody = p.getUserDataUCS2(count);
+ break;
+
+ case ENCODING_KSC5601:
+ mMessageBody = p.getUserDataKSC5601(count);
+ break;
+ }
+
+ if (VDBG) Rlog.v(LOG_TAG, "SMS message body (raw): '" + mMessageBody + "'");
+
+ if (mMessageBody != null) {
+ parseMessageBody();
+ }
+
+ if (!hasMessageClass) {
+ messageClass = MessageClass.UNKNOWN;
+ } else {
+ switch (mDataCodingScheme & 0x3) {
+ case 0:
+ messageClass = MessageClass.CLASS_0;
+ break;
+ case 1:
+ messageClass = MessageClass.CLASS_1;
+ break;
+ case 2:
+ messageClass = MessageClass.CLASS_2;
+ break;
+ case 3:
+ messageClass = MessageClass.CLASS_3;
+ break;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MessageClass getMessageClass() {
+ return messageClass;
+ }
+
+ /**
+ * Returns true if this is a (U)SIM data download type SM.
+ * See 3GPP TS 31.111 section 9.1 and TS 23.040 section 9.2.3.9.
+ *
+ * @return true if this is a USIM data download message; false otherwise
+ */
+ boolean isUsimDataDownload() {
+ return messageClass == MessageClass.CLASS_2 &&
+ (mProtocolIdentifier == 0x7f || mProtocolIdentifier == 0x7c);
+ }
+
+ public int getNumOfVoicemails() {
+ /*
+ * Order of priority if multiple indications are present is 1.UDH,
+ * 2.DCS, 3.CPHS.
+ * Voice mail count if voice mail present indication is
+ * received
+ * 1. UDH (or both UDH & DCS): mVoiceMailCount = 0 to 0xff. Ref[TS 23. 040]
+ * 2. DCS only: count is unknown mVoiceMailCount= -1
+ * 3. CPHS only: count is unknown mVoiceMailCount = 0xff. Ref[GSM-BTR-1-4700]
+ * Voice mail clear, mVoiceMailCount = 0.
+ */
+ if ((!mIsMwi) && isCphsMwiMessage()) {
+ if (mOriginatingAddress != null
+ && ((GsmSmsAddress) mOriginatingAddress).isCphsVoiceMessageSet()) {
+ mVoiceMailCount = 0xff;
+ } else {
+ mVoiceMailCount = 0;
+ }
+ Rlog.v(LOG_TAG, "CPHS voice mail message");
+ }
+ return mVoiceMailCount;
+ }
+}
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index ffc2de1..ec3d75e 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -76,7 +76,7 @@
<View xmlns:test="http://com.test"
attr="hey">
<Layout test:hello="hi" />
- <Layout>Some text</Layout>
+ <Layout>Some text\\</Layout>
</View>)EOF");
android::ResXMLTree tree;
@@ -128,7 +128,7 @@
ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT);
const char16_t* text = tree.getText(&len);
- EXPECT_EQ(StringPiece16(text, len), u"Some text");
+ EXPECT_EQ(StringPiece16(text, len), u"Some text\\");
ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG);
ASSERT_EQ(tree.getElementNamespace(&len), nullptr);
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index f8fa80e..7210d21 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -164,6 +164,7 @@
StringBuilder& Append(const android::StringPiece& str);
const std::string& ToString() const;
const std::string& Error() const;
+ bool IsEmpty() const;
// When building StyledStrings, we need UTF-16 indices into the string,
// which is what the Java layer expects when dealing with java
@@ -185,6 +186,8 @@
inline const std::string& StringBuilder::Error() const { return error_; }
+inline bool StringBuilder::IsEmpty() const { return str_.empty(); }
+
inline size_t StringBuilder::Utf16Len() const { return utf16_len_; }
inline StringBuilder::operator bool() const { return error_.empty(); }
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index fab2f19..d9ea1bc 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -18,7 +18,6 @@
#include <expat.h>
-#include <cassert>
#include <memory>
#include <stack>
#include <string>
@@ -41,6 +40,8 @@
std::unique_ptr<xml::Node> root;
std::stack<xml::Node*> node_stack;
std::string pending_comment;
+ std::unique_ptr<xml::Text> last_text_node;
+ util::StringBuilder pending_text;
};
/**
@@ -62,6 +63,19 @@
}
}
+static void FinishPendingText(Stack* stack) {
+ if (stack->last_text_node != nullptr) {
+ if (!stack->pending_text.IsEmpty()) {
+ stack->last_text_node->text = stack->pending_text.ToString();
+ stack->pending_text = {};
+ stack->node_stack.top()->AppendChild(std::move(stack->last_text_node));
+ } else {
+ // Drop an empty text node.
+ stack->last_text_node = nullptr;
+ }
+ }
+}
+
static void AddToStack(Stack* stack, XML_Parser parser,
std::unique_ptr<Node> node) {
node->line_number = XML_GetCurrentLineNumber(parser);
@@ -83,6 +97,7 @@
const char* uri) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
std::unique_ptr<Namespace> ns = util::make_unique<Namespace>();
if (prefix) {
@@ -99,6 +114,7 @@
static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
CHECK(!stack->node_stack.empty());
stack->node_stack.pop();
@@ -113,6 +129,7 @@
const char** attrs) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
std::unique_ptr<Element> el = util::make_unique<Element>();
SplitName(name, &el->namespace_uri, &el->name);
@@ -120,7 +137,9 @@
while (*attrs) {
Attribute attribute;
SplitName(*attrs++, &attribute.namespace_uri, &attribute.name);
- attribute.value = *attrs++;
+ util::StringBuilder builder;
+ builder.Append(*attrs++);
+ attribute.value = builder.ToString();
// Insert in sorted order.
auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(),
@@ -135,41 +154,38 @@
static void XMLCALL EndElementHandler(void* user_data, const char* name) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
CHECK(!stack->node_stack.empty());
// stack->nodeStack.top()->comment = std::move(stack->pendingComment);
stack->node_stack.pop();
}
-static void XMLCALL CharacterDataHandler(void* user_data, const char* s,
- int len) {
+static void XMLCALL CharacterDataHandler(void* user_data, const char* s, int len) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
- if (!s || len <= 0) {
+ const StringPiece str(s, len);
+ if (str.empty()) {
return;
}
// See if we can just append the text to a previous text node.
- if (!stack->node_stack.empty()) {
- Node* currentParent = stack->node_stack.top();
- if (!currentParent->children.empty()) {
- Node* last_child = currentParent->children.back().get();
- if (Text* text = NodeCast<Text>(last_child)) {
- text->text.append(s, len);
- return;
- }
- }
+ if (stack->last_text_node != nullptr) {
+ stack->pending_text.Append(str);
+ return;
}
- std::unique_ptr<Text> text = util::make_unique<Text>();
- text->text.assign(s, len);
- AddToStack(stack, parser, std::move(text));
+ stack->last_text_node = util::make_unique<Text>();
+ stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser);
+ stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser);
+ stack->pending_text.Append(str);
}
static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
XML_Parser parser = reinterpret_cast<XML_Parser>(user_data);
Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser));
+ FinishPendingText(stack);
if (!stack->pending_comment.empty()) {
stack->pending_comment += '\n';
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index a414afe..0fc3cec6 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -49,4 +49,23 @@
EXPECT_EQ(ns->namespace_prefix, "android");
}
+TEST(XmlDomTest, HandleEscapes) {
+ std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(
+ R"EOF(<shortcode pattern="\\d{5}">\\d{5}</shortcode>)EOF");
+
+ xml::Element* el = xml::FindRootElement(doc->root.get());
+ ASSERT_NE(nullptr, el);
+
+ xml::Attribute* attr = el->FindAttribute({}, "pattern");
+ ASSERT_NE(nullptr, attr);
+
+ EXPECT_EQ("\\d{5}", attr->value);
+
+ ASSERT_EQ(1u, el->children.size());
+
+ xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get());
+ ASSERT_NE(nullptr, text);
+ EXPECT_EQ("\\d{5}", text->text);
+}
+
} // namespace aapt
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a4ab64f..a1099f8 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -376,7 +376,9 @@
/**
* Flag indicating if this network is provided by a home Passpoint provider or a roaming
- * Passpoint provider.
+ * Passpoint provider. This flag will be {@code true} if this network is provided by
+ * a home Passpoint provider and {@code false} if is provided by a roaming Passpoint provider
+ * or is a non-Passpoint network.
*/
public boolean isHomeProviderNetwork;